はじめに
今回の記事は、前回に引き続き、私が受講しているE資格 JDLA認定プログラムの「ラビットチャレンジ」のレポート記事です。
今回のテーマは機械学習の線形回帰です。
コード実装を取り入れながら行っていきます。
概要
線形回帰(Linear Regression)とは、回帰問題の予測を行うアルゴリズムです。
教師あり学習の1つとしてよく挙げられます。
回帰問題とは、ある入力(離散あるいは連続値)から出力(連続値)を予測する問題のことです。
線形回帰とは言っても、内訳として線形回帰と非線形回帰に分けられます。
直線で予測するのが線形回帰で、曲線で予測するのが非線形回帰です。
回帰で扱うデータ
回帰問題は入力から出力を予測する問題ですが、扱うデータは次の通りです。
入力(各要素を説明変数または特徴量と呼ぶ)
\(m\)次元のベクトル(\(m=1\)の場合はスカラ)
$$
x=(x_{1},x_{2},…,x_{m})^T\in\mathbb{R}^m
$$
出力(目的変数)
$$
y\in\mathbb{R}^1
$$
パラメータ
モデルに含まれる推定すべき未知のパラメータがあります。
パラメータとは、特徴量が予測値に対してどのように影響を与えるかを決定する重みの集合のことです。
パラメータとして重みを調整することで、予測の精度が上がります。
入力と\(m\)次元パラメータの線形結合を出力するのが線形回帰モデルです。
重みは、それぞれの入力がモデルにどれだけ影響するかを決定します。
重みが0であれば、まったく影響しないことになります。
切片はもちろん、\(y\)軸との交点を表します。
$$
w=(w_{1},w_{2},…,w_{m})^T\in\mathbb{R}^m
$$
線形結合
線形結合とは、入力とパラメータの内積のことです。
入力ベクトルと未知のパラメータの各要素を掛け算し、足し合わせたものです。
また、内積なので、入力のベクトルが多次元でも出力は1次元(スカラ)になります。
$$
\hat{y}=w^{T}x+w_{0}=\sum_{j=i}^{m}{w_{j}x_{j}+w_{0}}
$$
上記のように、予測値にはハットをつけることが慣習です。
単回帰モデル
説明変数が1次元の場合((\m=1\))、単回帰モデルと呼びます。
データは回帰直線に誤差が加わり観測されていると仮定します。
$$
y=w_{0}+w_{1}x_{1}+\epsilon
$$
\(epsilon\)は誤差を表します。
モデルは直線になります。
重回帰モデル
説明変数が多次元の場合(\(m>1\))、重回帰モデルと呼びます。
単回帰モデルが直線であるのに対し、曲面で表されます。
データは、回帰曲面に誤差が加わり観測されていると仮定します。
$$
y=w_{0}+w_{1}x_{1}+…+w_{m}x_{m}+\epsilon
$$
データの分割
データはすべてを学習に使うのではなく、次のように分割して分析します。
・学習用データ
機械学習モデルの学習に利用するデータ
・検証用データ
学習済みモデルの精度を検証するためのデータ
・テスト用データ
モデルの性能を最後にチェックするためのデータ
分割する理由は、未知のデータに対してどれくらい精度が高いかを測りたいからです。
分割することで、モデルの汎化性能を測定することができます。
パラメータの推定
平均二乗誤差(残差平方和)
MSE(Mean Squared Error)と表記されることが多いです。
データとモデル出力の二乗誤差の和のことです。
この値が小さいほど直線とデータの距離が近いことを示します。
$$
MSE=\frac{1}{n}\sum_{i=1}^{n}{(\hat{y}-y)^2}
$$
平均平方二乗誤差
RMSE(Root Mean Squared Error)と表記されることが多いです。
MSEにRootをつけたものになります。
KaggleなどでもMSEより、RMSEを求められることが多い印象があります。
最小二乗法
学習データの平均二乗誤差を最小とするパラメータを探索します。
$$
\hat{w}=arg\, min_{w\in\mathbb{R}^m}\,MSE
$$
学習データの平均二乗誤差の最小化は、その勾配が0になる点を求めれば良いので、次のようになります。
・\(w\)に対して、微分した勾配が0になる点は、
$$
\frac{\partial}{\partial w}MSE=0
$$
・回帰係数(傾き)は以下になります。
$$
\hat{w}=(X^TX)^{-1}X^Ty
$$
・予測値を出すためには線形結合を算出するので、
$$
\hat{y}=X\hat{w}
$$
最尤法
誤差を正規分布に従う確率変数を仮定し、尤度関数の最大化を利用した推定も可能です。
線形回帰の場合には、最尤法による解は最小二乗法の解と一致します。
実装演習
使用するデータ
UCIのデータセットでよく使われている、ボストンの住宅データセットを使用します。
レコード数は506、カラム数は14です。
課題
部屋数と犯罪発生率から、その物件の住宅価格がいくらになるか予測します。
今回の課題は、部屋数が4で犯罪率が0.3の物件の価格がいくらになるかです。
必要なライブラリのインポート
先に、必要になるライブラリをインポートしておきます。
from sklearn.datasets import load_boston
from pandas import DataFrame
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
%matplotlib inline
データの格納
ボストンデータセットをbostonに格納します。
boston = load_boston()
# データの中身を確認
boston
単回帰モデル
データを取ってきたところで、単回帰モデルを作成する準備をします。
取ってきたデータでは、nd.array配列になっているのでDataFrameにする必要があります。
また、カラムの中には目的変数のPRICEというカラムは存在しないため、targetから持っていきます。
# 説明変数をDataFrameに変換
df = pd.DataFrame(boston.data, columns=boston.feature_names)
# 目的変数(PRICE)をDataFrameへ格納
df['PRICE'] = np.array(boston.target)
# 中身の確認
df
説明変数と目的変数を設定します。
ここは課題にあった通り、説明変数は部屋数で目的変数は住宅価格です。
# 説明変数
data = df.loc[:, ['RM']].values
# 目的変数
target = df.loc[:, 'PRICE'].values
変数をそれぞれ設定したら、早速モデルを作成していきます。
.fitに変数を与え、学習(平均二乗誤差を最小化)します。
# オブジェクト生成
model = LinearRegression()
# fit関数で学習
model.fit(data, target)
予測結果は次の通りになります。
# 予測
print(model.predict([[1]]))
print(model.predict([[4]]))
print(model.predict([[7]]))
# 出力
[-25.5685118]
[1.73781515]
[29.04414209]
部屋数1で予測された結果がマイナスとなっているので、少し極端な結果です。
課題の部屋数4や、7の場合はある程度予測が正しいと言えます。
推定された回帰係数と切片も出しておきます。
# 単回帰の回帰係数と切片を出力
print('推定された回帰係数: %.3f, 推定された切片 : %.3f' % (model.coef_, model.intercept_))
# 出力
推定された回帰係数: 9.102, 推定された切片 : -34.671
学習の結果を可視化してみます。
# 説明変数と目的変数のデータ点の散布図をプロット
plt.scatter(data, target, color = 'blue')
# 回帰直線をプロット
plt.plot(data, model.predict(data), color = 'red')
# 図のタイトル
plt.title('Regression Line')
# x軸のラベル
plt.xlabel('Average number of rooms [RM]')
# y軸のラベル
plt.ylabel('Prices in $1000\'s [MEDV]')
# グリッド線を表示
plt.grid()
# 図の表示
plt.show()
重回帰モデル
次に重回帰モデルで実行してみます。
やり方は単回帰モデルと変わりません。
説明変数を増やすだけです。課題どおり、犯罪率を追加して実行します。
# 説明変数
data2 = df.loc[:, ['CRIM', 'RM']].values
# 目的変数
target2 = df.loc[:, 'PRICE'].values
# オブジェクト生成
model2 = LinearRegression()
# fit関数でパラメータ推定
model2.fit(data2, target2)
print(model2.predict([[0.3, 4]]))
print(model2.predict([[0.5, 6]]))
# 出力
[4.24007956]
[20.9692334]
やはり単回帰モデルよりも良い精度で分析できていると言えます。
同様に、回帰係数と切片も出しておきます。
# 重回帰の回帰係数と切片を出力
print(model2.coef_)
print(model2.intercept_)
# 出力
[-0.26491325 8.39106825]
-29.24471945192995