線形回帰モデル

Pythonの機械学習用ライブラリscikit-learnを使って,線形回帰モデルを使って簡単な回帰問題にチャレンジしてみましょう.


0.ライブラリのインポート

In [1]:
import pandas as pd
import numpy as np

import sklearn

import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

np.set_printoptions(precision=4)
In [2]:
print("numpy :", np.__version__)
print("pandas :", pd.__version__)
print("sklearn :", sklearn.__version__)
print("seaborn :", sns.__version__)
print("matplotlib :", matplotlib.__version__)
numpy : 1.16.1
pandas : 0.24.2
sklearn : 0.20.2
seaborn : 0.9.0
matplotlib : 3.0.2

1. データの読込・整形

sklearn.datasetsからBostonデータセットを読み込みましょう.

In [3]:
# make data samples
from sklearn.datasets import load_boston
boston = load_boston()

次に,pandas DataFrame()クラスのインスタンスとして,変数df_data, df_target, dfを定義します.

参考: pandas.DataFrame — pandas 1.0.1 documentation

In [4]:
df_data   = pd.DataFrame(boston.data, columns = boston.feature_names)
df_target = pd.DataFrame(boston.target, columns = ['TARGET'])
df = pd.concat([df_target, df_data], axis = 1)
df = df.loc[:, ['TARGET', 'RM', 'DIS', 'INDUS', 'AGE', 'LSTAT', 'CRIM']]

df.head(10)
Out[4]:
TARGET RM DIS INDUS AGE LSTAT CRIM
0 24.0 6.575 4.0900 2.31 65.2 4.98 0.00632
1 21.6 6.421 4.9671 7.07 78.9 9.14 0.02731
2 34.7 7.185 4.9671 7.07 61.1 4.03 0.02729
3 33.4 6.998 6.0622 2.18 45.8 2.94 0.03237
4 36.2 7.147 6.0622 2.18 54.2 5.33 0.06905
5 28.7 6.430 6.0622 2.18 58.7 5.21 0.02985
6 22.9 6.012 5.5605 7.87 66.6 12.43 0.08829
7 27.1 6.172 5.9505 7.87 96.1 19.15 0.14455
8 16.5 5.631 6.0821 7.87 100.0 29.93 0.21124
9 18.9 6.004 6.5921 7.87 85.9 17.10 0.17004

データの要約統計量(サンプル数, 平均, 標準偏差, 四分位数, 中央値, 最小値, 最大値など)をみましょう.

In [5]:
df.describe().T
Out[5]:
count mean std min 25% 50% 75% max
TARGET 506.0 22.532806 9.197104 5.00000 17.025000 21.20000 25.000000 50.0000
RM 506.0 6.284634 0.702617 3.56100 5.885500 6.20850 6.623500 8.7800
DIS 506.0 3.795043 2.105710 1.12960 2.100175 3.20745 5.188425 12.1265
INDUS 506.0 11.136779 6.860353 0.46000 5.190000 9.69000 18.100000 27.7400
AGE 506.0 68.574901 28.148861 2.90000 45.025000 77.50000 94.075000 100.0000
LSTAT 506.0 12.653063 7.141062 1.73000 6.950000 11.36000 16.955000 37.9700
CRIM 506.0 3.613524 8.601545 0.00632 0.082045 0.25651 3.677083 88.9762

データの共分散行列を描画します.
対角成分は自分との共分散(相関)を表すため常に1.0となります.

In [6]:
df.corr()
Out[6]:
TARGET RM DIS INDUS AGE LSTAT CRIM
TARGET 1.000000 0.695360 0.249929 -0.483725 -0.376955 -0.737663 -0.388305
RM 0.695360 1.000000 0.205246 -0.391676 -0.240265 -0.613808 -0.219247
DIS 0.249929 0.205246 1.000000 -0.708027 -0.747881 -0.496996 -0.379670
INDUS -0.483725 -0.391676 -0.708027 1.000000 0.644779 0.603800 0.406583
AGE -0.376955 -0.240265 -0.747881 0.644779 1.000000 0.602339 0.352734
LSTAT -0.737663 -0.613808 -0.496996 0.603800 0.602339 1.000000 0.455621
CRIM -0.388305 -0.219247 -0.379670 0.406583 0.352734 0.455621 1.000000

データの散布図行列を描画します.
相関が大きい説明変数のペアについては, 多重共線性を考えるべきです.

In [7]:
sns.set()
sns.pairplot(df, height=2.0, diag_kind='hist', markers='+')
plt.show()

2. データの分割

pandas DataFrame()クラスの変数dfから,説明変数と目的変数に相当するデータをそれぞれ取り出し,numpy.ndarray()クラスの変数x, yへ格納します.

In [8]:
x = df.iloc[:, 1:] # set x to explanatory variable 
y = df.iloc[:, 0] # set y to response variable

全データをtrainデータとtestデータに分割します. すなわち,変数xx_trainx_testに, 変数yy_trainy_testに分けます.

In [9]:
# split data by Hold-out-method
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3, random_state = 0)

print()で配列の形状を確認してみましょう.

In [10]:
print("x_train: ", x_train.shape)
print("y_train: ", y_train.shape)
print("x_test: ", x_test.shape)
print("y_test: ", y_test.shape)
x_train:  (354, 6)
y_train:  (354,)
x_test:  (152, 6)
y_test:  (152,)
  • x_train: 6次元データが354コ格納されている.
  • y_train: 1次元データが354コ格納されている.
  • x_test: 6次元データが152コ格納されている.
  • y_test: 1次元データが152コ格納されている.

3. モデルの作成

ボストン市の住宅価格TARGETを目的変数にして線形回帰モデル$f$を作ります.$\beta$はモデルパラメータです.

$$ \begin{align} y &= f({\bf x} ; \beta) + u \\ &= f(x_1, \dots, x_6 ; \beta) + u \\ &= \beta_0 + \beta_1 x_1 + \cdots + \beta_6 x_6 + u \\ \\ u &\sim \mathcal{N}(0, \sigma^2) \end{align} $$
  • 目的変数 (1コ)
    • $y~~$ : TARGET - 住宅価格の中央値(単位: 千ドル)
  • 説明変数 (6コ)
    • $x_1$ : RM - 住居の平均部屋数
    • $x_2$ : DIS - 5つのボストン市の雇用施設からの重み付き距離
    • $x_3$ : INDUS - 非小売業の土地割合(単位: %)
    • $x_4$ : AGE - 1940年より前に建てられた物件割合(単位: %)
    • $x_5$ : LSTAT - 低所得労働者の割合(単位: %)
    • $x_6$ : CRIM - 人口あたり犯罪発生率(単位: %)
  • 誤差項 
    • $u$ : 平均0の正規分布に従うと仮定.
In [11]:
# Linear Regression
from sklearn.linear_model import LinearRegression
lr = LinearRegression()

4. モデルへデータを適合させる

データ$\mathcal{D} := \left\{ \left( \begin{array}{c} {\bf x}_1 \\ y_1 \end{array} \right), \dots, \left( \begin{array}{c} {\bf x}_n \\ y_n \end{array} \right) \right\}$を用いて計算される平均二乗誤差(Mean Squared Error, MSE)をパラメータ$\beta$の目的関数$J$とすれば,$J$を最小化するようなパラメータ$\beta^*$を求めることができます.

$$ \begin{align} \beta^* &= \underset{\bf \beta}{\rm argmin} ~ J(\beta) \\ J(\beta) &:= \sum_{i=1}^{n} {( y_i - f({\bf x}_i ; \beta) )}^2 \end{align} $$

実装上は,sklearn.linear_model.LinearRegression()クラスのインスタンスlrを作成し,trainデータx_train,y_trainを与えることで最適なモデルパラメータ$\beta^* = [\beta_0^* \dots,\beta_6^*]$を求めます.

In [12]:
lr.fit(x_train, y_train)
Out[12]:
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,
         normalize=False)

trainデータから推定された最適なモデルパラメータ$\beta^* = [\beta_0^* \dots,\beta_6^*]$の値を確認してみます.

In [13]:
print('  beta0  : {:.4e}'.format(lr.intercept_))
print('  beta1  : {:.4e}'.format(lr.coef_[0]))
print('  beta2  : {:.4e}'.format(lr.coef_[1]))
print('  beta3  : {:.4e}'.format(lr.coef_[2]))
print('  beta4  : {:.4e}'.format(lr.coef_[3]))
print('  beta5  : {:.4e}'.format(lr.coef_[4]))
print('  beta6  : {:.4e}'.format(lr.coef_[5]))
  beta0  : 8.6252e+00
  beta1  : 4.9834e+00
  beta2  : -1.2951e+00
  beta3  : -2.4170e-01
  beta4  : -3.6665e-02
  beta5  : -5.4298e-01
  beta6  : -1.4359e-01

5. モデルの評価

モデルlrに,入力としてx_train,x_testを与えて,y_train,y_testの値をそれぞれ予測させてみましょう.

In [14]:
# predictions
y_train_pred = lr.predict(x_train)
y_test_pred = lr.predict(x_test)

評価指標(Metrics)として,決定係数(R^2),平均二乗誤差(MSE), 平均絶対誤差(MAE)の値を計算してみましょう.

In [15]:
# model evaluation
from sklearn.metrics import mean_absolute_error, mean_squared_error

print('  R^2 (train) : {:.4f}'.format(lr.score(x_train, y_train)))
print('  R^2(test)   : {:.4f}'.format(lr.score(x_test, y_test)))
print('')
print('  MSE   (train) : {:.4e}'.format(mean_squared_error(y_train, y_train_pred)))
print('  MSE   (test) : {:.4e}'.format(mean_squared_error(y_test, y_test_pred)))
print('')
print('  MAE  (train) : {:.4e}'.format(mean_absolute_error(y_train, y_train_pred)))
print('  MAE  (test)  : {:.4e}'.format(mean_absolute_error(y_test, y_test_pred)))
  R^2 (train) : 0.6941
  R^2(test)   : 0.6063

  MSE   (train) : 2.5930e+01
  MSE   (test) : 3.2780e+01

  MAE  (train) : 3.6727e+00
  MAE  (test)  : 3.8834e+00

目的関数$J$をみれば明らかですが,trainデータに適合する(fitする)ようにモデルlrは設計されています. (モデルにとって)未知のデータであるtestデータに対する評価指標の値は,trainデータに対するものと比べて低くなります.

モデルがtrainデータに対して過度に適合した結果,testデータに対する性能が悪化する現象を「過学習(Over fitting)」といいます.