PythonでLassoを実装

[前回の「リッジ回帰についてまとめた」記事](https://qiita.com/kotamatsuoka/items/1ccb41ca278e400b6197)の続き。

## 目次
- Lassoとは
- Lassoの実装
- 制約の強さを変化させる
- 係数の大きさを図示
- おまけ

## 参考文献
- [Pythonではじめる機械学習](https://www.oreilly.co.jp/books/9784873117980/)

## Lassoとは
Lassoは、線形モデルによる回帰の一つ。

### 通常最小二乗法と同じ点
- 予測に用いられる式である

### 通常最小二乗法との異なる点
- リッジ回帰と同様に、係数($w$)が0になるように制約をかける

### リッジ回帰と異なる点
- 正則化には、L1正則化が用いられる
- L1正則化を使うと**完全に0になる**係数がある
    - 使われない特徴量が決まり、モデルが解釈しやすくなる

下の図でいうと、正則化をすることで、左に移動する。
![width=70](https://qiita-image-store.s3.amazonaws.com/0/185039/f889a769-6e6c-a3b8-27a9-5da761d929ae.png)

### 準備

```
$ pip install scikit-learn
$ pip install mglearn
```

## Lassoの実装

```python
import mglearn
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split

X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

lasso = Lasso().fit(X_train, y_train)
print(f"training dataに対しての精度: {lasso.score(X_train, y_train):.2}")
print(f"test set scoreに対しての精度: {lasso.score(X_test, y_test):.2f}")
# => training dataに対しての精度: 0.29
# => test set scoreに対しての精度: 0.21
```

### 考察
- training dataへの精度が悪い
- test dataへの精度が悪い
- 適合不足だと考えられる

適合度が低いので、モデルが簡潔すぎるかもしれません。
使われている特徴量の数を確認してみましょう。

```python
import numpy as np

print(f"使われている特徴量の数: {np.sum(lasso.coef_ != 0)}")
# => 使われている特徴量の数: 4
```

4つしか使われていないようです。
モデルをより複雑にできれば、より精度を上げることができそうです。

Lassoの正則化を調整するには、モデルの引数である **alpha**を変化させればよい。

## 制約の強さを変化させる
上のLasspの実装では、正則化の強さは、デフォルト値のままである。
正則化の強弱、つまり、モデルの複雑さは、**alpha**の値を変化させることにより、僕ら(モデルを構築する側)が決められる。

- alphaを増やす　-> 正則化が強くなる -> モデルは簡潔になる
- alphaを減らす　-> 正則化が弱くなる -> モデルは複雑になる

### aplhaを減らす
「alpha=0.01」にする。

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

print(f"training dataに対する精度: {lasso001.score(X_train, y_train):.2f}")
print(f"test dataに対する精度: {lasso001.score(X_test, y_test):.2f}")
print(f"使われている特徴量の数: {np.sum(np.sum(lasso001.coef_ != 0))}")
# => training dataに対する精度: 0.90
# => test dataに対する精度: 0.77
# => 使われている特徴量の数: 33
```

#### 考察
- 両方のデータに対しての精度が向上した
    - より複雑なモデルになっている
    - 上の図でいうと、右に遷移している

- 使われている特徴量の数が増えた

リッジ回帰において、同程度の精度を出すには、**104の特徴量**全てを使ってモデルを構築する必要があった。
なので、このLassoを用いたモデルでは、**33の特徴量**しか使われていないので、解釈性が増している。

### 補足: リッジ回帰
今回のデータセットを用いると、下記の条件でリッジ回帰とLassoは、ほぼ同程度の精度である。

- リッジ回帰: alpha=0.1
- Lasso: alpha=0.01

```python
import mglearn
from sklearn.linear_model import Ridge
from sklearn.model_selection import train_test_split

X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

ridge = Ridge(alpha=0.1).fit(X_train, y_train)
print(f"training dataに対しての精度: {ridge.score(X_train, y_train):.2}")
print(f"test set scoreに対しての精度: {ridge.score(X_test, y_test):.2f}")
# => training dataに対しての精度: 0.93
# => test set scoreに対しての精度: 0.77
```

## 係数の大きさを図示

```python
import matplotlib.pyplot as plt

plt.plot(lasso.coef_, 's', label="Lasso alpha=1")
plt.plot(lasso001.coef_, '^', label="Lasso alpha=0.01")
plt.plot(ridge.coef_, 'o', label="Ridge alpha=0.1")
plt.legend(ncol=2, loc=(0, 1.05))
plt.ylim(-25, 25)
plt.xlabel("Features index")
plt.ylabel("Coefficient magnitude")

plt.show()
```

![lasso.png](https://qiita-image-store.s3.amazonaws.com/0/185039/beb792b0-c309-f59e-e7be-77dfc7c92d18.png)


### 考察
「リッジ回帰: alpha=0.1」と「Lasso: alpha=0.01」は同程度の精度を示すが、係数のばらつきは「リッジ回帰: alpha=0.1」(緑○)の方がはるかに大きい。
つまり、このモデルを解釈するのは難しい。

よって、解釈性を求められる場合は、**Lasso**を用いるのが良いだろう。

## おまけ
分析結果とグラフとの対応を確認しながら学習すると、理解度が深まると思う。参考書にグラフが出てきた際は、その解説を読む前に、そのグラフを自分なりに読み解いてみると良いかもしれません。