【ラビットチャレンジ】機械学習レポート


# はじめに

ラビットチャレンジの「機械学習レポート」になります。

✅　応用数学レポート　
⭐️　機械学習レポート
深層学習前編（day1）レポート
深層学習前編（day2）レポート
深層学習後編（day3）レポート
深層学習後編（day4）レポート


## 1. 線形回帰モデル



### 要点


- 回帰問題
    - ある入力(離散あるいは連続値)から出力(連続値)を予測する問題
        - 直線で予測→線形回帰
        - 曲線で予測→非線形回帰

- 線形回帰モデル
    - 回帰問題を解くための機械学習モデルのひとつ
    - 教師あり学習
    - 入力とm次元パラメータの線形結合を出力するモデル


- 線形結合(入力とパラメータの内積)
    - 入力ベクトルと未知のパラメータの各要素を掛け算し足し合わせたもの
    - 入力ベクトルとの線形結合に加え、切片も足し合わせる

- モデルのパラメータ
    - モデルに含まれる推定すべき未知のパラメータ
    - 特徴量が予測値に対してどのように影響を与えるかを決定する重みの集合
        - 重みが大きければ(0であれば)、その特徴量は予測に大きな影響力を持つ
    - パラメータは未知 : 最小二乗法により推定


- 最小二乗法
    - 学習データの平均二乗誤差を最小とするパラメータを探索
    - 学習データの平均二乗誤差の最小化は、その勾配が0になる点を求めれば良い

- 平均二乗誤差 (残差平方和)
    - データの＝とモデル出力の二乗誤差
    - パラメータのみに依存する関数

- 最尤法による回帰係数の推定
    - 誤差を正規分布に従う確率変数を仮定し尤度関数の最大化を利用した推定
    - 線形回帰の場合はには、最尤法による解は最小二乗法の解と一致




### 関連学習



- 実装について
    - Sklearnに `linear_modoel.LinearRegression` というモジュールがあり線形回帰の実装が容易にできる
    - Parameters
        - fit_intercept
        - copy_x
        - n_jobs
        - positive
    - Attributes
        - coef_
        - rank_
        - singular_
        - intercept_
        - n_features_in_
        - feature_names_in_ 

```sample.py
>>> import numpy as np
>>> from sklearn.linear_model import LinearRegression
>>> X = np.array([[1, 1], [1, 2], [2, 2], [2, 3]])
>>> # y = 1 * x_0 + 2 * x_1 + 3
>>> y = np.dot(X, np.array([1, 2])) + 3
>>> reg = LinearRegression().fit(X, y)
>>> reg.score(X, y)
1.0
>>> reg.coef_
array([1., 2.])
>>> reg.intercept_
3.0...
>>> reg.predict(np.array([[3, 5]]))
array([16.])
```
※参考

https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html


## 2. 非線形モデル



### 要点



- 複雑な非線形構造を内在する現象に対して、非線形回帰モデリングを実施
    - データの構造を線形で捉えられる場合に限られる
    - 非線形な構造を捉えられる仕組みが必要

- 基底展開法
    - 回帰関数として、基底関数と呼ばれる既知の非線形関数とパラメータベクトルの線形結合を使用
    - 未知パラメータは線形回帰モデルと同様に最小2乗法や最尤法により推定
    - よく使われる基底関数
        - 多項式関数
        - ガウス型基底関数
        - スプライン関数
    - 基底展開法も線形回帰と同じ枠組みで推定可能


- 未学習(Underfitting) と 過学習(Overfitting)
    - 未学習(Underfitting)
        - 学習データに対して、十分小さな誤差が得られないモデル
            - (対策) モデルの表現力が低いため、表現力の高いモデルを利用する

    - 過学習(Overfitting)
        - 小さな誤差は得られたけど、テスト集合誤差との差が大きいモデル
            - 対策 : 学習データを増やす、不要な変数削除して表現力を抑止、正則化を利用して表現力を抑止

- モデルの複雑さを調整する2つの方法
    - 不要な基底関数を削除
        - 基底関数の数、位置やバンド幅によりモデルの複雑さが変化
        - 解きたい問題に対して多くの基底関数を用意してしまうと過学習の問題が起こるため、適切な基底関数を用意(CVなどで選択)

    - 正則化法
        - モデルの複雑さに伴って、その値が大きくなる正則化項を課した関数
        - 種類
            - ①Lasso推定量 (L1ノルムを利用)
                - スパース推定
                - いくつかのパラメータを正確に0に推定
                - 
            - ②Ridge推定量 (L2ノルムを利用)
                - 縮小推定
                - パラメータを0に近づけるよう推定


- 汎化性能
    - 学習に使用した入力だけでなく、これまで見たことない新たな入力に対する予測性能
    - バイアス(Bias)
        - 偏り
        - 予測値と正解値のずれ
        - ※ニューラルネットのニューロンの biasとは別物
    - バリアンス(Variance)
        - 予測値のばらつき
        - ばらつき誤差が大きいと訓練データのノイズまで学習しているので過学習状態 


---



## 3. ロジスティック回帰モデル


### 要点
- 分類問題(クラス分類)
    - ロジスティック回帰モデルは分類問題を解くための教師あり機械学習モデル
        - 入力とm次元パラメータの線形結合をシグモイド関数に入力
        - 出力はy=1になる確率の値になる

- シグモイド関数
    - 入力は実数、出力は必ず0-1の値
    - 単調増加関数
    - パラメータが変わるとシグモイド関数の形が変わる
    - 性質
        - シグモイド関数の微分は、シグモイド関数自身で表現することが可能
        - 尤度関数の微分を行う際にこの事実を利用すると計算が容易

- 最尤推定
    - ベルヌーイ分布
        - 数学において、確率pで1, 確率1-pで0をとる、離散確率分布
    - ベルヌーイ分布のパラメータの推定
        - ある分布を考えたとき、そのパラメータ(既知)によって、生成されるデータは変化
            - 0.3と0.8
        - データからそのデータを生成したであろう尤もらしい分布を推定したい(最尤推定)

- 尤度関数
    - データは固定し、パラメータを変化させる
    - 尤度関数を最大化するようなパラメータを選ぶ推定方法を最尤推定という

- 尤度関数を最大とするパラメータを探す(推定)
    - 対数をとると微分の計算が簡単
    - 対数尤度関数が最大になる点と尤度関数が最大になる点は同じ

- 確率的勾配降下法(SGD)
    - データを一つずつランダムに選んでパラメータを更新
    - 勾配降下法でパラメータを1回更新するのと同じ計算量でパラメータをn回更新できるので効率よく最適な回を探索可能




---



### 実装(ロジスティック回帰)

```py 
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

# 訓練データ生成
n_sample = 100
harf_n_sample = 50
var = .2

def gen_data(n_sample, harf_n_sample):
    x0 = np.random.normal(size=n_sample).reshape(-1, 2) - 1.
    x1 = np.random.normal(size=n_sample).reshape(-1, 2) + 1.
    x_train = np.concatenate([x0, x1])
    y_train = np.concatenate([np.zeros(harf_n_sample), np.ones(harf_n_sample)]).astype(np.int)
    return x_train, y_train

def plt_data(x_train, y_train):
    plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train, facecolor="none", edgecolor="b", s=50, label="training data")
    plt.legend()

#データ作成
x_train, y_train = gen_data(n_sample, harf_n_sample)
#データ表示
plt_data(x_train, y_train)
```
![スクリーンショット 2023-11-26 15.22.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/68920ae7-a628-092c-c48d-c24aac322f95.png)


```py 

def add_one(x):
    return np.concatenate([np.ones(len(x))[:, None], x], axis=1)

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sgd(X_train, max_iter, eta):
    w = np.zeros(X_train.shape[1])
    for _ in range(max_iter):
        w_prev = np.copy(w)
        sigma = sigmoid(np.dot(X_train, w))
        grad = np.dot(X_train.T, (sigma - y_train))
        w -= eta * grad
        if np.allclose(w, w_prev):
            return w
    return w

X_train = add_one(x_train)
max_iter=100
eta = 0.01
w = sgd(X_train, max_iter, eta)

xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

X_test = add_one(xx)
proba = sigmoid(np.dot(X_test, w))
y_pred = (proba > 0.5).astype(np.int)

plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
plt.contourf(xx0, xx1, proba.reshape(100, 100), alpha=0.2, levels=np.linspace(0, 1, 3))
```
![スクリーンショット 2023-11-26 15.23.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/8e37fe3f-a263-fed6-0180-b70e369fca29.png)

```py
# sklearn実装
from sklearn.linear_model import LogisticRegression
model=LogisticRegression(fit_intercept=True)
model.fit(x_train, y_train)
proba = model.predict_proba(xx)
y_pred = (proba > 0.5).astype(np.int)
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
plt.contourf(xx0, xx1, proba[:, 0].reshape(100, 100), alpha=0.2, levels=np.linspace(0, 1, 3))

```

![スクリーンショット 2023-11-26 15.23.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/bff601dd-ba89-a8c3-f6d0-d96a022a966a.png)

※numpyで実装すると細かいところの理解までできると改めて思いました。Sklearnで実装すると数行で学習から予測までできるので便利ですね

### 関連学習

- ROC曲線
    - どのくらいモデルが有用であるか確認できます。縦軸に感度、横軸に(1-特異度)をとります。曲線がグラフ左上の角に近い位置にあるほど感度と特異度が優れており、この値を最適なカットオフ値としています

```py
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(t_label, p_score)     
plt.plot(fpr, tpr, marker='o')
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.grid()
plt.savefig('plots/roc_curve.png')
```

- AUC(Area Under Curve)
    - AUC(Area Under Curve)とは、ROC曲線の下の面積で0~1をとる。ランダムの時は、0.5になります。値が1に近いほど性能が良いことになります。

```
from sklearn.metrics import roc_auc_score
print(roc_auc_score(y_true, y_score))
```

◼️ 参考 : 過去に自分でまとめた機械学習の評価指標

https://qiita.com/tatsuya11bbs/items/e3290c81982a461600b9



## 4. 主成分分析(PCA)

### 要点

- 多変量データの持つ構造をより少数個の指標に圧縮
    - 変数の個数を減らすことに伴う、情報の損失はなるべく小さくしたい
    - 少数変数を利用した分析や可視化が実現可能

- 係数ベクトルが変われば線形変換後の値が変化
    - 情報の量を分散の大きさと捉える
    - 線形変換後の変数の分散が最大となる射影軸を探索

- ラグランジュ関数を微分して最適解を求める

- 分散共分散行列は正定値対称行列→固有値は必ず0以上・固有ベクトルは直行

- 寄与率
    - 第1~元次元分の主成分で表示した時、固有値の和と元のデータの分散が一致
    - 寄与率:第k主成分の分散の全分散に対する割合
    - 累積寄与率:第1-k主成分まで圧縮した際の情報損失量の割合



### 実装

```py 
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

# 訓練データ生成
n_sample = 100

def gen_data(n_sample):
    mean = [0, 0]
    cov = [[2, 0.7], [0.7, 1]]
    return np.random.multivariate_normal(mean, cov, n_sample)

def plt_data(X):
    plt.scatter(X[:, 0], X[:, 1])
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
X = gen_data(n_sample)
plt_data(X)
```
![スクリーンショット 2023-11-26 15.12.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/e822de26-cbed-f51b-89d9-28da6233db21.png)

```py
#学習

n_components=2

def get_moments(X):
    mean = X.mean(axis=0)
    stan_cov = np.dot((X - mean).T, X - mean) / (len(X) - 1)
    return mean, stan_cov
    
def get_components(eigenvectors, n_components):
#     W = eigenvectors[:, -n_components:]
#     return W.T[::-1]
    W = eigenvectors[:, ::-1][:, :n_components]
    return W.T

def plt_result(X, first, second):
    plt.scatter(X[:, 0], X[:, 1])
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    # 第１主成分
    plt.quiver(0, 0, first[0], first[1], width=0.01, scale=6, color='red')
    # 第２主成分
    plt.quiver(0, 0, second[0], second[1], width=0.01, scale=6, color='green')

#分散共分散行列を標準化
meean, stan_cov = get_moments(X)
#固有値と固有ベクトルを計算
eigenvalues, eigenvectors = np.linalg.eigh(stan_cov)
components = get_components(eigenvectors, n_components)

plt_result(X, eigenvectors[0, :], eigenvectors[1, :])

```
![スクリーンショット 2023-11-26 15.13.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/e2a4f43e-784d-3d9c-af60-b1a1bf430dcd.png)


```py 
#射影

def transform_by_pca(X, pca):
    mean = X.mean(axis=0)
    return np.dot(X-mean, components)
Z = transform_by_pca(X, components.T)
plt.scatter(Z[:, 0], Z[:, 1])
plt.xlim(-5, 5)
plt.ylim(-5, 5)
```
![スクリーンショット 2023-11-26 15.14.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/ca79cb2b-f945-4330-5b06-42b1631bc308.png)

```py 
# 逆変換

mean = X.mean(axis=0)
X_ = np.dot(Z, components.T) + mean

plt.scatter(X_[:, 0], X_[:, 1])
plt.xlim(-5, 5)
plt.ylim(-5, 5)
```

```py 
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X)

print('components: {}'.format(pca.components_))
print('mean: {}'.format(pca.mean_))
print('covariance: {}'.format(pca.get_covariance())) 
plt_result(X, pca.components_[0, :], pca.components_[1, :])

```
![スクリーンショット 2023-11-26 15.16.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/a51c51d6-ce1b-da14-11ac-c5c29bc8a98b.png)

```py
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X)
plt_result(X, pca.components_[0, :], pca.components_[1, :])

print('components: {}'.format(pca.components_))
print('mean: {}'.format(pca.mean_))
print('covariance: {}'.format(pca.get_covariance()))
```
![スクリーンショット 2023-11-26 15.17.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223559/1d6eeffc-fa8d-2192-9d03-69e894da38fc.png)

※ Sklearnってやじゃり便利だと感じた。細かい設定までしたいなら公式ドキュメントをしっかり読みにいく癖をつけたいです。

## 5. サポートベクターマシン 

### 要点

- サポートベクターマシン(Support Vector Machine : SVM)
    - パターン認識手法
    - 基本的に2値分類を考えてクラスAとクラスBの間を直線(平面、超平面)で区切ってしまおうという考え方


- マージン最大化
    - マージン : 判別する境界とデータの距離
    - 2つのクラスに分ける決定境界の間にできる限り大きいマージンを確保する


- カーネルトリック
    - 一般的に2次元のもを直線を引いて2クラスに分けるが線形分離不可能な時にカーネルトリック使用
    - カーネルトリックにより非線形変換を施した上で、より高次元特徴空間に写像
    - 例
        - 線形カーネル
            - 訓練セットが大きい、特徴量が大きい
        - ガウスRBFカーネル
            - 訓練セットがそれほど大きくない 

- SVMメリデメ
    - メリット
        - サポートベクトルでトレーニングを行うのでメモリ効率が高い
        - 高次元空間で有効
        - 多次元空間に写像するカーネル関数を色々変えれる
        - 平均や分散を使わない
    - デメリット
        - SVMは境界面を設定するものなので確率は計算してくれない
        - 多クラス分類をするには分類器が増えるために計算量増える 



◼️ 以前に自分で書いた記事

https://qiita.com/tatsuya11bbs/items/6a68dbce1232e2c3d2e8



# 終わりに

以上が機械学習レポートになります。理論の具体のところまで理解したいのであればNumpyで実装してみる、雰囲気を掴みたいのであればSklearnで実装してみるのが良いと思いました。