線形回帰・Ridge回帰・Lasso回帰の違い

## ■ はじめに
今回は、線形回帰・Ridge回帰・Lasso回帰における
各特徴と違いについて、記事をまとめさせていただきます。
<br>
## ■ モジュール・データの準備

```python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import mglearn

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge, Lasso
from sklearn.linear_model import RidgeCV
from yellowbrick.regressor import AlphaSelection
```
mglearnとは、データの使用やプロットを視覚化するためのモジュールです。

今回は改良版のbostonデータセットを使用します。
元データと異なり、特徴量は104個となります。

```python

X, y = mglearn.datasets.load_extended_boston()

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

print('X:', X.shape)
print('y:', y.shape)
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: (506, 104)
# y: (506,)
# X_train: (379, 104)
# y_train: (379,)
# X_test: (127, 104)
# y_test: (127,)
```

## 1. 線形回帰
```python

lr = LinearRegression().fit(X_train, y_train)

# 決定係数（回帰モデルの予測の正確さを測る指標）
print('Train set score: {}'.format(lr.score(X_train, y_train)))
print('Test set score: {}'.format(lr.score(X_test, y_test)))

# Train set score: 0.9520519609032729
# Test set score: 0.6074721959665842
```
scoreは、決定係数（回帰モデルの予測の正確さを測る指標）です。

線形回帰において、手元にある訓練データの予測の精度は高いですが
テストデータ（未知のデータ）に対しては、予測の精度が低くなる傾向があります。

スポーツ（野球）に例えると、普段からストレートの球ばかり打つ練習をしていたために
本番の試合では、カーブの球に全く対応できなくなってしまうといった感じでしょうか。

実務やKaggleでは、未知のデータに対する汎化性能（本番への対応力）が重要となるため
今回のデータの場合、線形回帰モデルは不向きであることが分かります。
<br>
## 2. Ridge回帰
先ほどのような、訓練データに適応しすぎて
テストデータへの汎化性能が落ちることを「過学習」といいます。

これを防ぐために、Ridge回帰で正則化（パラメータ：alpha）を行います。

回帰係数（各特徴量の値）が大きくなったり、ばらつきがあると過学習が起こりやすくなりますが
パラメータのalphaを大きくすると、回帰係数は0に近づいていきます。

```python

ridge = Ridge(alpha=1).fit(X_train, y_train)

print('Training set score: {}'.format(ridge.score(X_train, y_train)))
print('Test set score: {}'.format(ridge.score(X_test, y_test)))

# Training set score: 0.885796658517094
# Test set score: 0.7527683481744752
```
訓練データへの適応は低下しますが、テストデータへの汎化性能は向上しました。
Ridge回帰のalphaはデフォルトで1となっているので、他の値でも試してみます。

```python

ridge10 = Ridge(alpha=10).fit(X_train, y_train)

print('Training set score: {:.2f}'.format(ridge10.score(X_train, y_train)))
print('Test set score: {:.2f}'.format(ridge10.score(X_test, y_test)))

# Training set score: 0.79
# Test set score: 0.64
```
alpha=1のときよりも、テストデータへの予測精度が低下しました。

```python

ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)

print('Training set score: {}'.format(ridge.score(X_train, y_train)))
print('Test set score: {}'.format(ridge.score(X_test, y_test)))

# Training set score: 0.885796658517094
# Test set score: 0.7527683481744752
```
こちらは、alpha=1のときと同じくらいの予測精度となります。

ここで、各回帰係数の大きさとばらつきについて
alpha=0.1, 1, 10の、3パターンを比較をしてみます。

```python

plt.plot(ridge10.coef_, 's', label='Ridge alpha=10')
plt.plot(ridge.coef_, 's', label='Ridge alpha=1')
plt.plot(ridge01.coef_, 's', label='Ridge alpah=0.1')

plt.plot(lr.coef_, 'o', label='LinearRegression')
plt.xlabel('Coefficient index')
plt.ylabel('Coefficient magnitude')

plt.hlines(0, 0, len(lr.coef_))
plt.ylim(-25, 25)

plt.legend()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/8bdbc5ed-3cc5-bbac-5f09-084f7476c53e.png)
横軸：104個の特徴量
縦軸：モデルにおける各回帰係数の大きさ

alpha=0.1, 1のように、データにある程度のばらつきがあった方が、スコアは高いことが分かります。
しかし線形回帰のようにばらつきが大きすぎたり（過学習）、alpha=10のように正則化を強くしすぎてしまうと
決定係数のスコアは低くなってしまいます。

先ほどはいくつかのalphaを代入してスコアの比較を行いましたが
最適なalphaについて、事前に調べる方法もあります。

まずパラメータalphaについて、値の探索する範囲を設定し
RidgeCVで訓練データについて交差検証を実行、AlphaSelectionで最適な値を求めプロットします。

```python

alphas = np.logspace(-10, 1, 500)

ridgeCV = RidgeCV(alphas = alphas)

alpha_selection = AlphaSelection(ridgeCV)
alpha_selection.fit(X_train, y_train)

alpha_selection.show()
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/05b53c24-5a98-82cb-bc8a-5ba9602790bf.png)
これにより、今回のRidge回帰における最適なパラメータ（alpha）の値が分かりました。

```python

ridge0069 = Ridge(alpha=0.069).fit(X_train, y_train)

print('Training set score: {}'.format(ridge.score(X_train, y_train)))
print('Test set score: {}'.format(ridge.score(X_test, y_test)))

# Training set score: 0.885796658517094
# Test set score: 0.7527683481744752
```
実際に試してみると、alpha=0.1, 1の時と同じ、高いスコアとなりました。

次に、線形回帰とRidge回帰（alpha=1）の比較として、学習曲線をプロットしてみます。

```python

mglearn.plots.plot_ridge_n_samples()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/c3fc99d2-3b6a-3b09-cb0c-a1b20f87d040.png)
横軸：データサイズ（総数）
縦軸：決定係数

線形回帰では過学習を起こしやすいため、訓練データのスコアは高いですが
テストデータにおいては、汎化性能がほぼゼロとなってしまっています。

ただし、データサイズが十分にあれば
Ridge回帰と同程度の汎化性能を持つことが分かります。
<br>
## 3. Lasso回帰

Ridge回帰と同様に、係数が0に近づくように制約をかけますが
かけ方が少し異なり、Lasso回帰はいくつかの係数が完全にゼロとなります。

Number of features used：使用した特徴量の数

```python

lasso = Lasso().fit(X_train, y_train)

print('Training set score: {:.2f}'.format(lasso.score(X_train, y_train)))
print('Test set score: {:.2f}'.format(lasso.score(X_test, y_test)))
print('Number of features used: {}'.format(np.sum(lasso.coef_ != 0)))

# Training set score: 0.29
# Test set score: 0.21
# Number of features used: 4
```

```python

lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)

print('Traing set score: {:.2f}'.format(lasso001.score(X_train, y_train)))
print('Test set score: {:.2f}'.format(lasso001.score(X_test, y_test)))
print('Number of features used: {}'.format(np.sum(lasso001.coef_ != 0)))

# Traing set score: 0.90
# Test set score: 0.77
# Number of features used: 33
```

```python

lasso00001 = Lasso(alpha=0.0001, max_iter=100000).fit(X_train, y_train)

print('Training set score: {:.2f}'.format(lasso00001.score(X_train, y_train)))
print('Test set score: {:.2f}'.format(lasso00001.score(X_test, y_test)))
print('Number of features used: {}'.format(np.sum(lasso00001.coef_ != 0)))

# Training set score: 0.95
# Test set score: 0.64
# Number of features used: 96
```
重要度の低い特徴量が0になりやすいように設計された誤差関数をもとに
訓練を行い、実際に使用する特徴量を決定しています。

今回の場合は、特徴量数が96個のように多かったり、4個のように少なすぎると
汎化性能が低くなっていることが分かります。

Lasso（alpha=0.0001）とRidge（alpha=1）について
回帰係数の大きさとばらつきを比較してみます。

```python

plt.plot(lasso.coef_, 's', label='Lasso alpha=1')
plt.plot(lasso001.coef_, '^', label='Lasso alpha=0.01')
plt.plot(lasso00001.coef_, 'v', label='Lasso alpha=0.0001')

plt.plot(ridge01.coef_, 'o', label='Ridge alpha=0.1')
plt.legend(ncol=2, loc=(0, 1.05))
plt.ylim(-25, 25)
plt.xlabel('Coefficient index')
plt.ylabel('Coefficient magnitude')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725082/c060b6e3-e524-b267-d38c-6dc346cc35a3.png)
横軸：104個の特徴量
縦軸：モデルにおける各回帰係数の大きさ

上図を見ると、やはり係数のばらつきもある程度は必要であり
Lasso（alpha=0.0001）はRidge（alpha=1）と同程度散らばっているので
決定係数のスコアも近しいことが分かります。
<br>
## ■ 結論
線形回帰を考えるのであれば、まずはRidge回帰でモデリングをして
不要な特徴量などがあると分かれば、Lasso回帰を行ってみるのが良い。

ElasticNet（RidgeとLassoの両方のパラメータを持つ）は精度では優れますが、調整に手間がかかります。
<br>
## ■ 参考文献
・[Pythonではじめる機械学習 - O'Reilly Japan](https://www.oreilly.co.jp/books/9784873117980/)