- 重回帰から派生したリッジ回帰・ラッソ回帰は、重回帰の過学習を抑制するしくみを持っていますが、具体的には次式のとおりです。
- リッジ回帰が重みの2乗を扱うのに対して、ラッソ回帰は重みの絶対値となっています。
- 前節のくり返しになりますが、従来の重回帰分析は、予測値と観測値の2乗和誤差を最小にする係数を求めます。これに、変数の数と重みに応じたペナルティを加えることで、係数が大きな値になることを防ぎます。
ここでは、ラッソ回帰もまじえて3つの重回帰モデルを比較してみたいと思います。
####⑴ ライブラリのインポート
# データ加工・計算・分析ライブラリ
import numpy as np
import pandas as pd
# グラフ描画ライブラリ
import matplotlib.pyplot as plt
%matplotlib inline
# 機械学習ライブラリ
import sklearn
####⑵ データの取得と読み込み
# データを取得
url = 'https://raw.githubusercontent.com/yumi-ito/datasets/master/datasets_auto_4variables_pre-processed.csv'
# 取得したデータをDataFrameオブジェクトとして読み込み
df = pd.read_csv(url, header=None)
# 列ラベルを設定
df.columns = ['width', 'height', 'horsepower', 'price']
print(df)
- 自動車に関する諸種の仕様のうちwidth(車幅)、height(車高)、horsepower(馬力)という3つを説明変数として、目的変数のprice(価格)を予測するためのデータです。
- データソースや概要などの詳細はこちら。https://qiita.com/y_itoh/items/9befbf47869d66337dad
- なお、不明値「?」および欠損値は削除済み、またデータ型もfloat型、int型に変換済みです。
# データ形状の確認
print('データ形状:', df.shape)
# 欠損値の確認
print('欠損値の数:{}\n'.format(df.isnull().sum().sum()))
# データ型の確認
print(df.dtypes)
####⑶ 訓練データ・テストデータの分割
# モデル構築のためのインポート
from sklearn.linear_model import Ridge, Lasso, LinearRegression
# データ分割のためのインポート
from sklearn.model_selection import train_test_split
- pandasの
drop()
関数でprice
列を削除して説明変数のみをxとし、またprice
のみをyと設定します。 - sklearnの
train_test_split
メソッドで、説明変数x、目的変数yをそれぞれ訓練データ(train)とテストデータ(test)に分けます。
# 説明変数と目的変数を設定
x = df.drop('price', axis=1)
y = df['price']
# 訓練データとテストデータに分割
X_train, X_test, Y_train, Y_test = train_test_split(x, y, test_size=0.5, random_state=0)
####⑷ モデルの生成と評価
- 重回帰、リッジ回帰、ラッソ回帰を一まとめに初期化して、
for
文によってモデル生成、訓練データの正解率、テストデータの正解率の算出までを一気に回します。
# 各クラスを初期化してdict型変数のmodelsに格納
models = {
'linear': LinearRegression(),
'ridge': Ridge(random_state=0),
'lasso': Lasso(random_state=0)}
# 正解率を格納するdict型変数を初期化
scores = {}
# 各モデルを順次生成し、正解率を算出して格納
for model_name, model in models.items():
# モデル生成
model.fit(X_train, Y_train)
# 訓練データの正解率
scores[(model_name, 'train')] = model.score(X_train, Y_train)
# テストデータの正解率
scores[(model_name, 'test')] = model.score(X_test, Y_test)
# dict型をpandasの1次元リストに変換
print(pd.Series(scores))
- dictオブジェクトをそのままfor文で回すと各要素のkeyが取得されますが、
items():
を使えば各要素のkeyとvalueの両方を取得できます。 - sklearnの
score()
関数で正解率を算出し、model_name
とtrain
かtest
のいずれかをセットでkeyとして格納します。
重回帰 | リッジ回帰 | ラッソ回帰 | |
---|---|---|---|
訓練データの正解率 | 0.733358 | 0.733355 | 0.733358 |
テストデータの正解率 | 0.737069 | 0.737768 | 0.737084 |
- 訓練データの正解率は、リッジ回帰<ラッソ回帰=重回帰という大小関係で、またテストデータの正解率は、リッジ回帰>ラッソ回帰>重回帰となりました。
- ラッソ回帰に注目すると、訓練データでの正解率は重回帰と同率で、テストデータでは、リッジ回帰ほどではないが重回帰を少し上回っています。
- ただし、**正則化の強さを指定するパラメータ$λ$**はまだ手つかずのままで、scikit-learnのデフォルトはどちらも$λ=1.0$です。
そこで、正則化パラメータを変更して比較してみたいと思います。
##正則化パラメータ
- 正則化の強さを指定するパラメータ$λ$は、大きくすればペナルティの影響が強くなるので、回帰係数の絶対値は小さく抑えられます。
- パラメータの設定は、クラスを初期化してモデルのひな型を作る際、引数に
alpha=
で指定します。$alpha=10.0$で試してみます。
# パラメータ設定
alpha = 10.0
# 各クラスを初期化してmodelsに格納
models = {
'ridge': Ridge(alpha=alpha, random_state=0),
'lasso': Lasso(alpha=alpha, random_state=0)}
# 正解率を格納するdict型変数を初期化
scores = {}
# 各モデルを順次実行し、正解率を格納していく
for model_name, model in models.items():
model.fit(X_train, Y_train)
scores[(model_name, 'train')] = model.score(X_train, Y_train)
scores[(model_name, 'test')] = model.score(X_test, Y_test)
print(pd.Series(scores))
- 以下に、正則化パラメータ$λ$を段階的に変えてみた結果を示します。
λ | Ridge(train) | Ridge(test) | Lasso(train) | Lasso(test) |
---|---|---|---|---|
1 | 0.733355 | 0.737768 | 0.733358 | 0.737084 |
10 | 0.733100 | 0.743506 | 0.733357 | 0.737372 |
100 | 0.721015 | 0.771022 | 0.733289 | 0.740192 |
200 | 0.705228 | 0.778607 | 0.733083 | 0.743195 |
400 | 0.680726 | 0.779004 | 0.732259 | 0.748795 |
500 | 0.671349 | 0.777338 | 0.731640 | 0.751391 |
1000 | 0.640017 | 0.767504 | 0.726479 | 0.762336 |
- この例では、まずリッジ回帰は、概して訓練データでは下振れぎみでテストデータになると逆転するという傾向がみられ、$λ$が大きくなるほどその傾向は顕著です。対してラッソ回帰は、全般に挙動がゆるやかで、$λ$が大きくなっても訓練データでの歩留りがよく、かつテストデータの正解率は$λ$が大きくなるにつれて少しずつ高くなります。
- 単に係数を2乗するか絶対値をとるかの違いですが、ペナルティの大きさは係数によってラッソ回帰の方が大きくもなりますし、要するに影響のしかたのタイプが異なるのです。
- それぞれの正則化の効果について、係数との関係も考え合わせてさらに一歩を進めてみたいと思います。