ラビットチャレンジ　レポート（その4　機械学習-線形回帰・非線形回帰）

# 機械学習
そもそも機械学習モデリングはどのようなフローで行うか？

  - 問題設定：どのような問題を機械学習に解決させるか（そもそも機械学習を使うかどうか）
  - データ選定：どのようなデータを使うか
  - データの前処理：モデルに学習させられるようにデータを変換
  - 機械学習モデルの選定：どの機械学習モデルを利用するか
  - モデルの学習：パラメータ推定
  - モデルの評価：ハイパーパラメータの選定やモデル精度の測定

今回の話は「機械学習モデルの選定」の際に、選択肢となる機械学習モデルの概要と具体的な手法の中身。

## 線形回帰

### 回帰問題とは
ある入力値（離散、あるいは連続値）から出力（連続値）を推定する問題。

### 線形・非線形の違い
ざっくりと言えば、直線で予測するのが線形回帰、曲線で予測するのが非線形回帰。
関数$f$が線形であるためには、以下の2条件を見たす必要がある。

  * 加法性：$f(x+y)=f(x)+f(y)$が成り立つ。
  * 斉次性：定数$k$を用いて、$f(kx) = kf(x)$が成り立つ。

### 線形回帰モデル
教師あり学習で、入力とパラメータの線形結合によって表されるモデル。
パラメータは

```math
\boldsymbol{w} = (w_1,w_2, \dots,w_m)^\top \in \mathbb{R}^m
```
で表すことができ、これを用いると、入力系列$x$を用いて出力$y$の推定値$\hat{y}$は以下のように、パラメータと入力の線形結合で書くことができる。

```math
\hat{y} = \boldsymbol{w}^T \boldsymbol{x} + w_0 = \sum_{i=1}^{m}w_i x_i + w_0
```
このパラメータ$\boldsymbol{w}$が学習するべきパラメータで、最小二乗法によって求める。
最小二乗法は、モデル出力と教師データの二乗誤差の和を最小化することによって、パラメータを求める手法。
ここで出てくる、最小化するべき関数は、**MSE（平均二乗誤差）**と呼ばれ、以下の式で表される。
※$N$はデータ数

```math
\mathrm{MSE}_{train}= \frac{1}{N} \sum_{i=1}^{N}(y_i - \hat{y}_i)^2
```
この$\mathrm{MSE}_{train}$を最小化するためには、この関数を$\boldsymbol{w}$で偏微分すればよい。
すると、

```math
\cfrac{\partial }{\partial \boldsymbol{w}} \mathrm{MSE}_{train} = 0 \\
\Rightarrow \cfrac{\partial }{\partial \boldsymbol{w}} \left(\frac{1}{N} \sum_{i=1}^{N}(y_i - \hat{y}_i)^2 \right)=0 \\
\Rightarrow \hat{\boldsymbol{w}} = (\bf{X}^\top \bf{X})^{\mathrm{-1}}\bf{X}^\top \boldsymbol{y}
```

### ハンズオン
まずは、適当な1次関数にノイズを載せたデータを作成、pythonのnumpyのみを用いて、線形回帰を最小二乗法を用いて行う

```python
import numpy as np
import matplotlib.pyplot as plt

#ランダムデータ生成
x = np.array(range(0,100,1))
y = np.array([2 * value + 10 + np.random.normal(0,20,1) for value in x])

#線形回帰をする関数
def LinearRegression(x,y):
    a_child = np.dot(x, y) - x.sum() * y.sum() / len(x)
    a_mother = (x**2).sum() - x.sum()**2 / len(x)
    a = a_child / a_mother
    b = (y.sum() - a * x.sum()) / len(x)
    return a, b

a,b = LinearRegression(x,y)
y_estimate = a * x + b

#結果を描画
plt.figure(figsize = (8,6))
plt.scatter(x,y, label = 'data')
plt.plot(x, y_estimate, color = 'orange', label = 'regression')
plt.legend(fontsize = 12)
plt.show()
```
結果が以下。
青いデータ点が作成したデータ。オレンジの直線が線形回帰で求めた直線。

![線形回帰-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2437549/acc00dd8-2001-1ff8-7619-0863998b4017.png)

ボストンの住宅データセットを線形回帰モデルで分析。今度はsklearnを用いる。

```python
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn import linear_model

boston_raw_data = load_boston()

#説明変数と目的変数読み込み
X = boston_raw_data.data
y = boston_raw_data.target

#訓練とテストに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)

#学習
model = linear_model.LinearRegression()
model.fit(X_train, y_train)

#評価値を見てみる
print(model.score(X_train, y_train))
print(model.score(X_test, y_test))
```
結果は

```python
0.7730135569264234
0.5892223849182507
```
となる。予測精度としてはあまり高くはない。

次に、データセットの項目のうち、'CRIM'（犯罪率）と'RM'（部屋数）のみを用いて回帰を行ってみる。

```python
import pandas as pd
import numpy as np

df = pd.DataFrame(X, columns = boston_raw_data.feature_names).assign(MEDV=np.array(y))
X = np.array(df.loc[:,['CRIM','RM']])
y = np.array(df.loc[:,['MEDV']])

#訓練とテストに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)

#学習
model = linear_model.LinearRegression()
model.fit(X_train, y_train)

#評価値を見てみる
print(model.score(X_test, y_test))
print(model.predict([[0.3,4]]))
```

結果は以下。

```python
0.3872466946091787
[[3.72851066]]
```
精度は落ちた。このモデルでは、犯罪率が0.3で部屋数が4の物件の価格は、約3.72ということになる。（課題）

### データの分割とモデルの汎化性能測定
上記のハンズオン中でボストンの住宅価格データセットを用いる際、データの分割を行った。これは、データへの当てはまりの良さではなく、未知のデータに対してどれくらい精度が高いかを測定するため。

データは2つに分割する。

  * 学習用データ：機械学習モデルの学習に利用するデータ
  * 検証用データ：学習済みモデルの精度を検証するためのデータ

学習用データでモデルを学習後、検証用データでモデルの未知のデータへの当てはまりの良さ（汎化性能）を測定する。

## 非線形回帰
ここまでやってきたような線形回帰で捉えられるデータ構造は限られている。
そこで、学習するパラメータ$\boldsymbol{w}$と目的変数との関係が線形ではない、非線形回帰モデルを用いる。
例えば、以下のようなもの。

```math
y = e^x \\
y = w_0 + w_1 x_1^2 \\
y = w_0 + w_1 \sin{x_1} + w_2 \cos{x_2}
```

ただし、**基底関数法**を用いれば、上のようなモデルも線形回帰と同じように扱うことが可能。
例えば、$\phi(x)=x^2$や$\phi(x)=\sin{x}$などを用いると、上記の式も

```math
y = w_0 + w_1 \phi(x_1) + w_2 \phi(x_2) + \dots + w_m \phi(x_m)
```
と変形できる。この式は、パラメータ$\boldsymbol{w}$については線形な関係が成り立っているので、線形回帰と同じような扱いが可能。

より正確な書き方をすると、

```math
y_i = w_0 + \Sigma_{j=1}^{m}w_j \phi_j(\boldsymbol{x}_i)+ \varepsilon_i
```
この式における、$\phi_j(\boldsymbol{x}_i)$を基底関数と呼ぶ。
この時よく使われる基底関数は

  * 多項式関数
  * ガウス型基底関数
  * スプライン型/Bスプライン関数

など。

### 未学習と過学習
回帰を実行するにあたりきをつけなければいけないこと。

  * 未学習：学習データに対して、十分小さな誤差が得られないモデル。もっと表現力の高いモデルを利用する必要がある。
  * 過学習：学習データに対する誤差は小さいが、テストデータに対する誤差が大きいモデル。が奇襲データを増やしたり、モデルの表現力を下げる等の対策が必要。

特に過学習については、基底関数を多く用いると頻発するため、基底関数の数や位置・バンド幅などを調整する必要がある。

### 正則化法
モデルの複雑さに伴ってパラメータの値が大きくなる正則化項に罰則項を課した関数を最小化。
過学習が起きるモデルのパラメータの値が極端に大きいことが背景にある。
与える罰則項にはいくつか種類がある。

まず、**L1ノルム**を利用するのが**Lasso回帰**。L1ノルムは以下の形で表される。

```math
||\boldsymbol{w}|| = |w_0| + |w_1| + \dots + |w_m| 
```

続いて、**L2ノルム**を利用するのが**Ridge回帰**。L2ノルムは以下の形で表される。

```math
||\boldsymbol{w}||^2 = |w_0|^2 + |w_1|^2 + \dots + |w_m|^2
```

### ハンズオン

まずは、実験に使うデータ作成

```python
#データの作成
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

n=100

def true_func(x):
    z = 1-48*x+218*x**2-315*x**3+145*x**4
    return z 

def linear_func(x):
    z = x
    return z

#真の関数からノイズを伴うデータを生成

#真の関数からデータ生成
data = np.random.rand(n).astype(np.float32)
data = np.sort(data)
target = true_func(data)

#ノイズを加える
noise = 0.5 * np.random.randn(n) 
target = target  + noise

#描画
plt.figure(figsize = (8,6))
plt.scatter(data, target)
plt.title('NonLinear Regression')
```
![非線形回帰-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2437549/7de5f1b5-99be-bf8b-8899-9aff15ccd45e.png)

このデータに対して、sklearnのKernelRidgeクラスを用いて非線形回帰を行う。

```python
from sklearn.kernel_ridge import KernelRidge

data = data.reshape(-1,1)
target = target.reshape(-1,1)

#学習
model = KernelRidge(alpha=0.0002, kernel='rbf')
model.fit(data, target)

#推定
p_kridge = model.predict(data)

#グラフ描画
plt.figure(figsize = (8,6))
plt.scatter(data, target, label='data')
plt.plot(data, p_kridge, color='orange', linestyle='-', linewidth=3, markersize=6, label='kernel ridge')
plt.legend()
```

![非線形回帰-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2437549/295cd818-eeae-7663-88c3-9263362e5d8c.png)

ここで用いたrbfは放射基底関数。予測はデータによく適合しているように見える。

### 検証方法
学習データと検証データの分け方、パラメータの探し方にも種類がある。

##### ホールドアウト法
有限のデータを学習用とテスト用の2つに分割し、「学習用データで学習させたモデル（パラメータを求めたモデル）でテスト用データの予測精度を測定」する使い方をする。前述した方法。
手元にデータが大量にない限りは、性能評価があまりよくならない。

##### クロスバリデーション（交差検証）
手元にあるデータをn分割（nは5や10が多い）、そのうち一つをテスト用、残りを学習用としたモデル評価をn回行う。その精度の平均をモデルの汎化性能とする（この精度の平均をCV値と呼ぶ）。

##### グリッドサーチ
全てのチューニングパラメータの組み合わせを試して評価値を算出し、最もいい評価値を持つパラメータの組み合わせを選出する。