ある量$y$とほかの量$x_1,x_2,\cdots, x_n$との関係をデータから割り出したいという問題を考える。
ある現象に対して要因と考えられるいくつかを挙げ、それらの寄与度を調べたり、要因となる諸量の値から現象を予測するというような問題ある。分野によっていろいろな呼び名があるが、ここでは、目的変数、説明変数という用語を用いる。
両者が線形の関係 $$ y = w_1 x_1 + w_2 x_2 + \cdots + w_n x_n $$ にあるとして、その係数$w_1, \cdots, w_n$をデータから決定する問題を線形重回帰分析(モデル)と呼ぶ。(あとで述べるように、非線形な関係を求める場合へも適用可能である。)
この問題を数値的に求めるライブラリとして、この講義では主に、scikit-learnを用いることにする。
sckit-learnでは(多くの場合、ほかのツール同様であるが)、項目を列として定義し、行にデータを並べることが多い。下の例は、次節で扱うデータの例である。
「2次元配列」としてデータを用意するのであるが、どちらを行、どちらを列としてもよいのではなく、上述のように並べる必要があることに注意しよう。
説明変数が一つの場合も、説明変数はデータは1列N行の配列に作成する。
統計ソフトの多くに付属しているボストンデータを例に重回帰について試してみる。目的は線形重回帰を理解することにあるが、非線形単回帰との関連についても考察する。
ボストンデータとは、ボストン市内の住宅物件の価格と様々な社会的データをまとめたものであり、それぞれの社会的データから価格を予測(評価)する手法を考察しようとするものである。
以下のような項目(Attribute)と物件の価格のデータが500件あまり用意されている。
Attribute | description |
---|---|
CRIM | 人口 1 人当たりの犯罪発生数 |
ZN | 25,000平方フィート以上の住居区画の占める割合 |
INDUS | 小売業以外の商業が占める面積の割合 |
CHAS | チャールズ川によるダミー変数 (1: 川の周辺, 0: それ以外) |
NOX | NOx の濃度 |
RM | 住居の平均部屋数 |
AGE | 1940 年より前に建てられた物件の割合 |
DIS | 5 つのボストン市の雇用施設からの距離 (重み付け済) |
RAD | 環状高速道路へのアクセスしやすさ |
TAX | $10,000 ドルあたりの不動産税率の総計 |
PTRATIO | 町毎の児童と教師の比率 |
B | 町毎の黒人 (Bk) の比率を次の式で表したもの。 1000(Bk – 0.63)2 |
LSTAT | 給与の低い職業に従事する人口の割合 (%) |
これらの項目を説明変数(feature 特徴量)、住宅物件の価格を目的変数(target)として、両者が線形な関係にあると仮定して、その係数を求めることを線形重回帰と呼ぶ。
説明変数を$(x_1, x_2, \cdots, x_M)$、目的変数を$y$で表せば、
$$ y = w_1 x_1 + w_2 x_2 + \cdots + w_M x_M $$参考:"scikit-learn boston 重回帰"くらいで検索すると山のようにページがでてくるが、上位のものとしては
そのほか、ランダムフォレストを使った分析もある。(後の回に、若干ふれる)
重回帰は、複数の 以下では、最初にあげたページに沿って実行(訓練)し、係数を決めた後、テストデータを使って予測の良さを調べてみる。
from sklearn.datasets import load_boston
boston = load_boston() # データセットの読み込み
# Bostonデータの項目確認
print(boston.DESCR)
.. _boston_dataset: Boston house prices dataset --------------------------- **Data Set Characteristics:** :Number of Instances: 506 :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target. :Attribute Information (in order): - CRIM per capita crime rate by town - ZN proportion of residential land zoned for lots over 25,000 sq.ft. - INDUS proportion of non-retail business acres per town - CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise) - NOX nitric oxides concentration (parts per 10 million) - RM average number of rooms per dwelling - AGE proportion of owner-occupied units built prior to 1940 - DIS weighted distances to five Boston employment centres - RAD index of accessibility to radial highways - TAX full-value property-tax rate per $10,000 - PTRATIO pupil-teacher ratio by town - B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town - LSTAT % lower status of the population - MEDV Median value of owner-occupied homes in $1000's :Missing Attribute Values: None :Creator: Harrison, D. and Rubinfeld, D.L. This is a copy of UCI ML housing dataset. https://archive.ics.uci.edu/ml/machine-learning-databases/housing/ This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University. The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic prices and the demand for clean air', J. Environ. Economics & Management, vol.5, 81-102, 1978. Used in Belsley, Kuh & Welsch, 'Regression diagnostics ...', Wiley, 1980. N.B. Various transformations are used in the table on pages 244-261 of the latter. The Boston house-price data has been used in many machine learning papers that address regression problems. .. topic:: References - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261. - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.
以下で、randome_state=0として分割の仕方を固定している。実際には、何種類もの分割を試して、モデルの良さを確かめる、交差検定を行う。
from sklearn.model_selection import train_test_split
# 入出力の切り分け
x = boston['data'] # 物件の情報
t = boston['target'] # 家賃
x_train, x_test, t_train, t_test = train_test_split(x, t, test_size=0.3, random_state=0) # データの70%を訓練用に、残りをテスト用に
print(x_train[:3,:]) # 試しに最初の3行を出力
[[1.62864e+00 0.00000e+00 2.18900e+01 0.00000e+00 6.24000e-01 5.01900e+00 1.00000e+02 1.43940e+00 4.00000e+00 4.37000e+02 2.12000e+01 3.96900e+02 3.44100e+01] [1.14600e-01 2.00000e+01 6.96000e+00 0.00000e+00 4.64000e-01 6.53800e+00 5.87000e+01 3.91750e+00 3.00000e+00 2.23000e+02 1.86000e+01 3.94960e+02 7.73000e+00] [5.57780e-01 0.00000e+00 2.18900e+01 0.00000e+00 6.24000e-01 6.33500e+00 9.82000e+01 2.11070e+00 4.00000e+00 4.37000e+02 2.12000e+01 3.94670e+02 1.69600e+01]]
順序は
その後、
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(x_train, t_train) # training
model.score(x_train, t_train) # 決定係数R^2の出力
0.7645451026942549
決定係数
テストデータの実測値を$(t_1, t_2, \cdots, t_N)$、予測(推定)値を$(y_1, y_2, \cdots, y_N)$としたとき $$ R^2 = 1 - \frac{\sum_{i=1}^N (y_i - t_i)^2}{ \sum_{i=1}^N (t_i - \bar{t})^2} $$
# テストデータの予測値と実測値比較
x_predict = model.predict(x_test) # テストデータ(家賃)の予測値
import matplotlib.pyplot as plt
plt.plot(x_predict, t_test, "o")
plt.xlabel("predicted value")
plt.ylabel("observed value")
Text(0, 0.5, 'observed value')
授業では、もっぱら説明変数が1つで目的変数との関係が非線形な場合を扱う。
ここで線形重回帰に触れたのは、重回帰の場合
$$ y = w_1 x_1 + w_2 x_2 + \cdots + w_M x_M $$で使ったLinearRegressionは非線形な回帰で用いることができるという点である。
一つの説明変数$x$と目的変数$y$が非線形な関係にあると推量される場合、、例えばその関係が「べき関数」だとすれば、各項目(Attribute)を$x$のべき乗として $$ y = w_1 x + w_2 x^2 + \cdots w_M x^M $$
のようにかけば、係数$\{w_i\}$を線形重回帰で推定することと同じであうことがわかるだろう。
$N$個のデータがあるとき、説明変数としては、各行に$x^1, x^2,\cdots, x^{M-1}$の値をセットした$N$行の行列を与えることになる。
[補足] 一般に、1つの説明変数$x$、1つの目的変数$y$の間に非線形な関係がある場合、$x$に対するいくつかの関数を用意し、それの線形結合によって表す。
$$ y = w_1\phi_1 (x) + w_2 \phi_2 (x) + \cdots + w_M\phi_M (x) $$のように書いて、線形重回帰と同様のスキームで扱う。このような場合、 $\{\phi_i\}$のセットを「基底」と呼び、データの性質によって、べき以外に、ガウシアンやシグモイド関数($\tanh$関数)、フーリエ基底(三角関数)を用いることが多い。フーリエ基底の場合はwaveletと呼ばれる。
# 回帰係数の出力
print(model.coef_)
[-1.21310401e-01 4.44664254e-02 1.13416945e-02 2.51124642e+00 -1.62312529e+01 3.85906801e+00 -9.98516565e-03 -1.50026956e+00 2.42143466e-01 -1.10716124e-02 -1.01775264e+00 6.81446545e-03 -4.86738066e-01]
# 回帰係数とfeatureの名前を結合してみる
import numpy as np
coef_names = np.stack([boston.feature_names, model.coef_])
print(coef_names)
[['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO' 'B' 'LSTAT'] ['-0.12131040096834629' '0.04446642542889598' '0.011341694511898402' '2.5112464244913792' '-16.23125290285598' '3.8590680098251915' '-0.009985165654562847' '-1.500269563265005' '0.2421434660581058' '-0.011071612402085984' '-1.0177526384185978' '0.006814465447642474' '-0.48673806564491884']]
# 上記が見にくいので、pandasのDataFrameに直して出力してみる
import pandas as pd
result= pd.DataFrame(coef_names)
# NOXが負の大きな値に、RMが正の比較的大きな値になっていることがわかる。
result
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT |
1 | -0.12131040096834629 | 0.04446642542889598 | 0.011341694511898402 | 2.5112464244913792 | -16.23125290285598 | 3.8590680098251915 | -0.009985165654562847 | -1.500269563265005 | 0.2421434660581058 | -0.011071612402085984 | -1.0177526384185978 | 0.006814465447642474 | -0.48673806564491884 |
# 行と列を入れ替え
result = pd.DataFrame(coef_names).T
# 絶対値で降順にソート
result['abs_coef'] = np.abs(result[1].astype(float))
result.sort_values('abs_coef', ascending=False)
0 | 1 | abs_coef | |
---|---|---|---|
4 | NOX | -16.23125290285598 | 16.231253 |
5 | RM | 3.8590680098251915 | 3.859068 |
3 | CHAS | 2.5112464244913792 | 2.511246 |
7 | DIS | -1.500269563265005 | 1.500270 |
10 | PTRATIO | -1.0177526384185978 | 1.017753 |
12 | LSTAT | -0.48673806564491884 | 0.486738 |
8 | RAD | 0.2421434660581058 | 0.242143 |
0 | CRIM | -0.12131040096834629 | 0.121310 |
1 | ZN | 0.04446642542889598 | 0.044466 |
2 | INDUS | 0.011341694511898402 | 0.011342 |
9 | TAX | -0.011071612402085984 | 0.011072 |
6 | AGE | -0.009985165654562847 | 0.009985 |
11 | B | 0.006814465447642474 | 0.006814 |
係数の大きさは、単純には寄与度を表すが、注意が必要 ⇒ 一番下の記述を参照!
from sklearn.datasets import load_boston
boston = load_boston() # データセットの読み込み
# データをpandasのDataFrameに入れて処理
import pandas as pd
boston_df = pd.DataFrame(boston.data, columns=boston.feature_names)# 説明変数(data)
boston_df['PRICE'] = boston.target # 目的変数(target)をDataFrameの列として追加
boston_df
CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT | PRICE | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.00632 | 18.0 | 2.31 | 0.0 | 0.538 | 6.575 | 65.2 | 4.0900 | 1.0 | 296.0 | 15.3 | 396.90 | 4.98 | 24.0 |
1 | 0.02731 | 0.0 | 7.07 | 0.0 | 0.469 | 6.421 | 78.9 | 4.9671 | 2.0 | 242.0 | 17.8 | 396.90 | 9.14 | 21.6 |
2 | 0.02729 | 0.0 | 7.07 | 0.0 | 0.469 | 7.185 | 61.1 | 4.9671 | 2.0 | 242.0 | 17.8 | 392.83 | 4.03 | 34.7 |
3 | 0.03237 | 0.0 | 2.18 | 0.0 | 0.458 | 6.998 | 45.8 | 6.0622 | 3.0 | 222.0 | 18.7 | 394.63 | 2.94 | 33.4 |
4 | 0.06905 | 0.0 | 2.18 | 0.0 | 0.458 | 7.147 | 54.2 | 6.0622 | 3.0 | 222.0 | 18.7 | 396.90 | 5.33 | 36.2 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
501 | 0.06263 | 0.0 | 11.93 | 0.0 | 0.573 | 6.593 | 69.1 | 2.4786 | 1.0 | 273.0 | 21.0 | 391.99 | 9.67 | 22.4 |
502 | 0.04527 | 0.0 | 11.93 | 0.0 | 0.573 | 6.120 | 76.7 | 2.2875 | 1.0 | 273.0 | 21.0 | 396.90 | 9.08 | 20.6 |
503 | 0.06076 | 0.0 | 11.93 | 0.0 | 0.573 | 6.976 | 91.0 | 2.1675 | 1.0 | 273.0 | 21.0 | 396.90 | 5.64 | 23.9 |
504 | 0.10959 | 0.0 | 11.93 | 0.0 | 0.573 | 6.794 | 89.3 | 2.3889 | 1.0 | 273.0 | 21.0 | 393.45 | 6.48 | 22.0 |
505 | 0.04741 | 0.0 | 11.93 | 0.0 | 0.573 | 6.030 | 80.8 | 2.5050 | 1.0 | 273.0 | 21.0 | 396.90 | 7.88 | 11.9 |
506 rows × 14 columns
import matplotlib.pyplot as plt
# 全データ項目の散布図
pd.plotting.scatter_matrix(boston_df,figsize=(20,20))
plt.plot()
[]
一番下の散布図に注目してみよう。
縦軸は、目的変数の「価格」である。
RMと相関、NOXとLSTATが逆相関になっていることが目立つだろう。
RMとNOXの重回帰係数の絶対値が大きくなっていることと符合する。が、LSTATの係数が小さいのはなぜだろうか?
各項目(列)の統計諸量を見てみよう。
# Calculate the statistical values of columns
boston_df.describe()
CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT | PRICE | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 | 506.000000 |
mean | 3.613524 | 11.363636 | 11.136779 | 0.069170 | 0.554695 | 6.284634 | 68.574901 | 3.795043 | 9.549407 | 408.237154 | 18.455534 | 356.674032 | 12.653063 | 22.532806 |
std | 8.601545 | 23.322453 | 6.860353 | 0.253994 | 0.115878 | 0.702617 | 28.148861 | 2.105710 | 8.707259 | 168.537116 | 2.164946 | 91.294864 | 7.141062 | 9.197104 |
min | 0.006320 | 0.000000 | 0.460000 | 0.000000 | 0.385000 | 3.561000 | 2.900000 | 1.129600 | 1.000000 | 187.000000 | 12.600000 | 0.320000 | 1.730000 | 5.000000 |
25% | 0.082045 | 0.000000 | 5.190000 | 0.000000 | 0.449000 | 5.885500 | 45.025000 | 2.100175 | 4.000000 | 279.000000 | 17.400000 | 375.377500 | 6.950000 | 17.025000 |
50% | 0.256510 | 0.000000 | 9.690000 | 0.000000 | 0.538000 | 6.208500 | 77.500000 | 3.207450 | 5.000000 | 330.000000 | 19.050000 | 391.440000 | 11.360000 | 21.200000 |
75% | 3.677083 | 12.500000 | 18.100000 | 0.000000 | 0.624000 | 6.623500 | 94.075000 | 5.188425 | 24.000000 | 666.000000 | 20.200000 | 396.225000 | 16.955000 | 25.000000 |
max | 88.976200 | 100.000000 | 27.740000 | 1.000000 | 0.871000 | 8.780000 | 100.000000 | 12.126500 | 24.000000 | 711.000000 | 22.000000 | 396.900000 | 37.970000 | 50.000000 |
それぞれの属性の平均値や標準偏差がばらついていることがわかる。これらを平均値をそろえ(ゼロに統一)、スケールして標準偏差をそろえることにより「平等に扱う」手続きを データの「標準化」(standardization)という。
標準化のモジュールはsklearnに含まれている。 標準化は次のように行うことができる時間があればこれを試してみよう。
# データの標準化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler().fit(X_train)
X_train_scaled = pd.DataFrame(scaler.transform(X_train), index=X_train.index.values, columns=X_train.columns.values)
X_test_scaled = pd.DataFrame(scaler.transform(X_test), index=X_test.index.values, columns=X_test.columns.values)
Bostonデータについて正規化を行って、精度の改善がどの程度可能かテストしてみなさい。
ボストンデータのような様々な属性のデータを使って、予測する場合には、木構造にデータを分割するランダムフォレストによる回帰分析が良く用いられる。 たとえば、アルゴリズムの説明を抜きにシンプルなコードのみを説明したページとして
https://hinomaruc.hatenablog.com/entry/2019/11/14/200857
を上げて置く。(6週目ではこれを実際に使ってみる。)
%%html
<link rel="stylesheet" type="text/css" href="custom.css">