Scikit-learnカリフォルニア住宅価格データセットで深層ニューラルネットワークしてみる

機械学習

前回の記事で、『Scikit-learn乳がん診断データセットで深層ニューラルネットワークしてみる
これをやりました。中々の正解率がでましたので、他でもやってみようということで、
今回は回帰ですが、やっていこうと思います。

データの確認とか準備とか

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
plt.style.use("ggplot")

from keras import models
from keras import layers
from keras.callbacks import EarlyStopping
from sklearn.model_selection import train_test_split

from sklearn.datasets import fetch_california_housing

前回とほぼ同じですが、データセットが違います。

ordata=fetch_california_housing()

df=pd.DataFrame(ordata.data,columns=ordata.feature_names)
df.head()                           

こないして、データをみてやります。

MedIncHouseAgeAveRoomsAveBedrmsPopulationAveOccupLatitudeLongitude
08.3252416.9841271.023813222.55555637.88-122.23
18.3014216.2381370.9718824012.10984237.86-122.22
27.2574528.2881361.0734464962.8022637.85-122.24
35.6431525.8173521.0730595582.54794537.85-122.25
43.8462526.2818531.0810815652.18146737.85-122.25
MedInc区画の収入の中央値
HouseAge区画内の住宅の平均築年数
AveRooms区画内の住宅の平均部屋数
AveBedrms区画内の住宅の平均ベッドルーム数
Population区画の人口
AveOccup区画の平均住宅占有率
Latitude区画の緯度
Longitude区画の経度

1行1区画(ブロック)で、上記各々のデータが入っています。目的変数(target)には、区画ごとの
住宅価格の中央値が入っています。8つの特徴量で、住宅価格を回帰しましょう、という問題です。

目的変数を別途DFにして、

df_MedHouseVal=pd.DataFrame(ordata.target,columns=["MedHouseVal"])

くっつける

df_=pd.concat([df_MedHouseVal,df],axis=1)
df_.head()
MedHouseValMedIncHouseAgeAveRoomsAveBedrmsPopulationAveOccupLatitudeLongitude
04.5268.3252416.9841271.023813222.55555637.88-122.23
13.5858.3014216.2381370.9718824012.10984237.86-122.22
23.5217.2574528.2881361.0734464962.8022637.85-122.24
33.4135.6431525.8173521.0730595582.54794537.85-122.25
43.4223.8462526.2818531.0810815652.18146737.85-122.25

この状態でinfoを見てやります。

df_.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 9 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   MedHouseVal  20640 non-null  float64
 1   MedInc       20640 non-null  float64
 2   HouseAge     20640 non-null  float64
 3   AveRooms     20640 non-null  float64
 4   AveBedrms    20640 non-null  float64
 5   Population   20640 non-null  float64
 6   AveOccup     20640 non-null  float64
 7   Latitude     20640 non-null  float64
 8   Longitude    20640 non-null  float64
dtypes: float64(9)
memory usage: 1.4 MB

ということで、20640区画のデータが欠損値なしでそろっていることが分かります。
データの数が多いので、各々の分布を見てやります。

df_.hist(bins=200,figsize=(25,25),color="red")

MedHouseValとMedIncとHouseAgeに外れ値があるようなので、これを除去します。

df_.describe()
MedHouseValMedIncHouseAgeAveRoomsAveBedrmsPopulationAveOccupLatitudeLongitude
count206402064020640206402064020640206402064020640
mean2.0685583.87067128.639495.4291.0966751425.4773.07065535.63186-119.57
std1.1539561.89982212.585562.4741730.4739111132.46210.386052.1359522.003532
min0.149990.499910.8461540.33333330.69230832.54-124.35
25%1.1962.5634184.4407161.0060797872.42974133.93-121.8
50%1.7973.5348295.2291291.0487811662.81811634.26-118.49
75%2.647254.74325376.0523811.09952617253.28226137.71-118.01
max5.0000115.000152141.909134.06667356821243.33341.95-114.31

分布図とdescribeから、各々最大値のところで外れ値となっているっぽいので、

df_= df_[df_["MedHouseVal"] < df_["MedHouseVal"].max()]
df_= df_[df_["MedInc"] < df_["MedInc"].max()]
df_= df_[df_["HouseAge"] < df_["HouseAge"].max()]

こうして除きます。もっかい見てやると、

df_[["MedHouseVal","MedInc","HouseAge"]].hist(bins=200,figsize=(25,25),color="red")

外れ値が消えてくれました。infoを再度見ておくと、

df_.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 18570 entries, 0 to 20639
Data columns (total 9 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   MedHouseVal  18570 non-null  float64
 1   MedInc       18570 non-null  float64
 2   HouseAge     18570 non-null  float64
 3   AveRooms     18570 non-null  float64
 4   AveBedrms    18570 non-null  float64
 5   Population   18570 non-null  float64
 6   AveOccup     18570 non-null  float64
 7   Latitude     18570 non-null  float64
 8   Longitude    18570 non-null  float64
dtypes: float64(9)
memory usage: 1.4 MB

ご覧の通り、データが18570個に減りました。例のごとく、このデータを標準化して、学習用やら検証用やらテスト用やらにやっていきます。

x_df=df_.iloc[:,1:].reset_index(drop="True")
y_df=df_.iloc[:,:1].reset_index(drop="True")

x_=x_df.values
y=y_df.values

from sklearn.preprocessing import StandardScaler
scaler=StandardScaler()
x=scaler.fit_transform(x_)

x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=0)
x_train,x_train_val,y_train,y_train_val=train_test_split(x_train,y_train,test_size=0.3,random_state=0)
print(x_train.shape,x_train_val.shape,x_test.shape)
print(y_train.shape,y_train_val.shape,y_test.shape)

(9099, 8) (3900, 8) (5571, 8)
(9099, 1) (3900, 1) (5571, 1)

やりました。

モデルの構築と学習

さて、モデル構築ですが、前回は2値分類だったので、出力層の活性化関数をシグモイドに指定していましたが、今回は回帰なので、リニアに指定します。

model=models.Sequential()
model.add(layers.Dense(500,activation="relu",input_dim=8))
model.add(layers.Dense(250,activation="relu"))
model.add(layers.Dense(100,activation="relu"))
model.add(layers.Dense(50,activation="relu"))
model.add(layers.Dense(1,activation="linear"))

こんな感じにしてみました。

model.summary()

Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_10 (Dense)            (None, 500)               4500      
                                                                 
 dense_11 (Dense)            (None, 250)               125250    
                                                                 
 dense_12 (Dense)            (None, 100)               25100     
                                                                 
 dense_13 (Dense)            (None, 50)                5050      
                                                                 
 dense_14 (Dense)            (None, 1)                 51        
                                                     =================================================================
Total params: 159,951
Trainable params: 159,951
Non-trainable params: 0
____________________________________________________________

約16万個のパラメータですって。
前回同様、他もろもろ指定して、学習!

model.compile(optimizer="adam",
              loss="mean_squared_error",
              metrics=["mean_squared_error"])

callbacks=[EarlyStopping(monitor="val_mean_squared_error",patience=5)]

model_results=model.fit(x_train,
                  y_train,
                  epochs=100,
                  batch_size=500,
                  verbose=1,
                  callbacks=callbacks,
                  validation_data=(x_train_val,y_train_val))

で、回帰なので決定係数を見マース。

from sklearn import metrics

print(f"train_r2_score:{metrics.r2_score(y_train,model.predict(x_train))}")
print(f"val_r2_score:{metrics.r2_score(y_train_val,model.predict(x_train_val))}")
print(f"test_r2_score:{metrics.r2_score(y_test,model.predict(x_test))}")

train_r2_score:0.7893249139012292
val_r2_score:0.7225655524573514
test_r2_score:0.750738987864861

決定係数で、テストデータに対してこれですから、かなり優秀な部類と思います。
ちなみに、ランダムフォレストでもサクっとやってみると、

from sklearn.ensemble import RandomForestRegressor

rtree=RandomForestRegressor(n_estimators=500,min_samples_leaf=10,random_state=0).fit(x_train,y_train.ravel())

print(f"train_r2_score:{metrics.r2_score(y_train,rtree.predict(x_train))}")
print(f"val_r2_score:{metrics.r2_score(y_train_val,rtree.predict(x_train_val))}")
print(f"test_r2_score:{metrics.r2_score(y_test,rtree.predict(x_test))}")

train_r2_score:0.8410055117828509
val_r2_score:0.7226441833988966
test_r2_score:0.7456163453839174

過学習ぎみですが、この子も優秀!