E検定で出てくる機械学習分野についてまとめ

# はじめに
本記事ではE検定で出てくる以下の機械学習分野について、その概要とサンプルプログラムをまとめたものとなります。
E検定の勉強にお役立てください。

* 線形回帰モデル
* 非線形回帰モデル
* ロジスティック回帰モデル
* 主成分分析
* 最近傍・K近傍アルゴリズム
* k-means
* サポートベクターマシン

# そもそも、機械学習とは？
人工知能の一種で、コンピュータにデータを学習させ、その学習結果を生かして特定のタスクを行うコンピュータアルゴリズムです。
※機械学習：https://ja.wikipedia.org/wiki/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92

例えば、タスクとコンピュータに学習させるデータは以下のようなものがあります。
タスク：人の年齢、BMIからその人の推定寿命を予測するタスク
学習データ：年齢、BMI、その人の寿命

# 回帰モデル
回帰問題と呼ばれるある入力(数値)から出力値を予測する問題で利用するモデルが回帰モデルの一つです。
つまり、出力値$y$、入力値$(x_{1},x_{2},...,x_{n})$があった時、回帰モデルは$y = f(x_{1},x_{2},...,x_{n})$となるような$f$を求めます。

## 線形回帰モデル
### 概要
回帰問題が入力値$(x_{1},x_{2},...,x_{n})$と係数の線形結合で予測できる際に使用するモデルです。
具体的には以下の式で表されるモデルで$w_{0},w_{1},w_{2},...,w_{n}$はパラメータで、線形回帰モデルはこのパラメータを適切な値に調整していくことで出力値$y$に出来るだけ近づけていきます。
なお、用語の定義としてこのパラメータ$w_{1},w_{2},...,w_{n}$を回帰係数、$w_{0}$を切片、$x_{1},x_{2},...,x{n}$を説明変数と呼びます。

```math
y = \sum_{j=1}^{n} w_{j}x_j+w_{0}
```

そして、肝心のパラメータの更新方法ですが、最小二乗法によって求めます。
まず、以下の様に行列表現が出来ますが、最小二乗法を使用して、モデルの出力値ベクトルを学習データの出力値ベクトルになるべく近づけます。

```math
(モデルの出力値ベクトル) = (学習データの説明変数行列) \times (回帰係数ベクトル) + (切片ベクトル)
```

そのために平均二乗誤差を使って、学習データの出力値とモデルの出力値との誤差を算出し、その誤差を最小にします。
誤差が最小であることと=誤差の勾配が0であることは同値のため、平均二乗誤差を微分した結果を求め、その結果を回帰係数に反映する方法を最小二乗法と呼ばれます。
最小二乗法で更新する回帰係数の式は以下の通りです。

```math
(回帰係数ベクトル) = ((学習データの説明変数行列)^{T}(学習データの説明変数行列))^{-1}(学習データの説明変数行列)^{T}(学習データの出力値ベクトル)
```
### 実装
簡単な例として、$y=-3x+5$を線形回帰してみます。使用するライブラリはsklearnとします。

●コード

```python
# 各種ライブラリのインポート
from pandas import DataFrame
import numpy as np
from sklearn.linear_model import LinearRegression

# 学習データの出力値生成関数
def ans_func(x):
  return -3 * x + 5

# x軸の配列を生成
x = np.linspace(0, 10, 5, dtype=int)
# 学習する際、入力としてxを転置する(横ベクトルから縦ベクトルへ変換)必要があるため、reshapeで変換
rx = x.reshape(-1, 1)
# 学習データの出力値
y_true = ans_func(x)

# 線形回帰モデル作成
linear_model = LinearRegression()

# 学習
linear_model.fit(rx, y_true)

# 回帰係数を確認
print("linear_model.coef:%s" % (linear_model.coef_))
print("linear_model.intercept_:%s" % (linear_model.intercept_))

# 学習データにない値で予測してみる
linear_model.predict([[100]])
```

●実行結果

以下の通り、傾きが-3、切片が4.999...、となっており、もともと予測したかった$y=-3x+5$に近似できたことがわかります。
また、予測値も$-3*100+5 = -295$とぴったり一致していることもわかります。

```
linear_model.coef:[-3.]
linear_model.intercept_:4.9999999999999964
array([-295.])
```

## 非線形回帰モデル
線形回帰モデルではデータの入力値と出力に線形関係(入力値の値が変わればそれに比例して出力も変わる)に対して、有効なモデルでしたが、例えば、$y=x^{2}+3x+1$の様な非線形関係の場合、線形モデルでは表現することが出来ません。
そこで、そういった非線形関係を予測するために非線形モデルを用います。
非線形回帰モデルは線形回帰モデルと同じく学習に最小二乗法、最尤法を使用しますが、異なる点として、線形回帰の回帰係数を使用していましたが、非線形回帰では回帰関数を使用し、モデルの表現力を向上させています。
回帰関数としては概ね多項式関数(例：$x^{n}$)やガウス型基底関数(例：$exp((x- \mu)^{2}/2h)$)が使用されます。

### 未学習と過学習
学習データが少ない場合、十分誤差をモデルに反映できず表現力が低いモデルを未学習といいます。
また、学習データに対して最小の誤差は得られたが、学習データ以外のデータに関して誤差が大きなモデルを過学習と呼びます。
(つまり、学習データのみに特化したモデルですが、機械学習の大事なポイントは未知のデータを予測することなので、あまり機械学習として意味がないモデルです)

### 正則化
一般的に基底関数が多いと過学習が起こりやすく、過学習を避けるために正則化と呼ばれる方法を使います。
正則化は基底関数のパラメータ更新時に誤差分更新値をモデルの複雑さに応じて少なくすることで学習データに特化しない様にしています。
正則化にはL2ノルムを使用する方法、L1ノルムを使用する方法があります。

### モデルの精度について
モデルの精度はモデルの予測結果と検証データの結果の一致・不一致の組み合わせによって以下の様に精度を考えます。

モデルの予想結果が陽、検証データの結果が陽⇒真陽性(TP)
モデルの予想結果が陽、検証データの結果が偽⇒偽陰性(FP)
モデルの予想結果が偽、検証データの結果が陽⇒偽陽性(FN)
モデルの予想結果が偽、検証データの結果が偽⇒真陰性(TN)

正解率=$\frac{TP+TN}{TP+FN+FP+TN}$
再現性=$\frac{TP}{TP+FN}$
適合率=$\frac{TP}{TP+FP}$
F値=再現性と適合率の調和平均

### 実装
今回は$-2x^{2}+5$を非線形回帰するコード例を示します。

●コード

```python
# 各種ライブラリのインポート
from pandas import DataFrame
import numpy as np
from sklearn.kernel_ridge import KernelRidge

# 学習データの出力値生成関数
def ans_func(x):
  return -2* x ** 2 + 5

# x軸の配列を生成(線形回帰の場合、5つのデータを使用していたが、非線形回帰の場合、学習データが少なすぎて、未学習となるため、500個データを用意する)
x = np.linspace(0, 1000, 500, dtype=int)
# 学習する際、入力としてxを転置する(横ベクトルから縦ベクトルへ変換)必要があるため、reshapeで変換
rx = x.reshape(-1, 1)
# 学習データの出力値
y_true = ans_func(x)

# 線形回帰モデル作成
nonlinear_model = KernelRidge(alpha=0.0002, kernel='rbf')

# 学習
nonlinear_model.fit(rx, y_true)

# 回帰係数を確認
print("nonlinear_model.dual_coef_:%s" % (nonlinear_model.dual_coef_))

# 学習データにない値で予測してみる
nonlinear_model.predict([[100]])
```

●実行結果

今回入力値100とし、期待される出力値は-19995であり、モデルの出力値と近いため、未知のデータを予測できていることが確認できます。

```
nonlinear_model.dual_coef_:[ 5.04697947e+00 -2.61994482e+00 -2.57682849e+01 -6.43473432e+01
 -1.18358048e+02 -1.87800383e+02 -2.72674347e+02 -3.72979941e+02
 -4.88717165e+02 -6.19886019e+02 -7.66486503e+02 -9.28518617e+02
...
 -1.90204955e+06 -1.90906609e+06 -1.95240980e+06 -1.99935418e+06]
array([-19991.14311153])
```

# ロジスティック回帰モデル
ロジスティック回帰モデルは分類問題といういくつかの入力値からそのクラスに属するか否かを分類する問題で使用する機械学習モデルです。
例えば、ある道路の車と人の1日の交通量とその日の交通事故の有無を学習させ、その道路の車と人の交通量からその日交通事故が起きるか・起きないかを判定するような問題で使用するモデルとなります。

具体的にはロジスティック回帰モデルは以下のような式となります。

```math
P(x) = \sigma (w_{m}x_{m}+...+w_{1}x_{1}+w_{0})\\
\sigma (x) = \frac{1}{1+exp(-ax)} ・・・シグモイド関数
```

見ての通り、線形回帰モデルと似た構造ですが、異なる点として、回帰係数と説明変数の計算をした後、シグモイド関数を使用しています。
シグモイド関数は実数空間で定義される関数で0～1の値を取りえます。この関数を使用して、その分類の確からしさ(確率)を出力します。
確率を出力するため、分類結果を0、1のどちらかとして出力したい場合は、例えば、$P(x) >= 0.5$の場合は1、$P(x) < 0.5$の場合は0という様に出力値を加工します。

ロジスティック回帰モデルの学習は線形回帰と同じく最小二乗法、最尤法のどちらかで行います。

### 実装
データセットとしてsklearnに付属しているiris(アヤメ(花)の品種)データセットを使用します。
今回、説明変数から花の品種が「setosa」である予測モデルを作成します。

●コード

```python
# 各種ライブラリをロード
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
import numpy as np
from sklearn.linear_model import LogisticRegression 
from sklearn.model_selection import train_test_split

# irisデータセットを取得
iris = load_iris()

# setosaの場合、1、それ以外は0となるように修正
iris.target = np.where(iris.target==0,1,0)

# 学習データと検証データを分割
X_train, X_test, Y_train, Y_test = train_test_split(iris.data, iris.target)
# ロジスティック回帰モデルを作成
model = LogisticRegression()
# ロジスティック回帰モデルを学習させる
model.fit(X_train, Y_train)
# 予測
pred = model.predict(X_test)
# 精度を算出
accuracy_score(Y_test, pred)
```

●実行結果
精度は100%となりました。そのため、花の品種が「setosa」であるか否かのモデル作成は成功しました。

```
1.0
```

# 主成分分析
教師なし学習の一つで、多次元のデータの次元をなるべく情報の損失を最小限にして小さくすることが出来るモデルです。
多次元のデータの次元を削減することで、2,3次元まで落とし込めることが出来れば可視化が出来たりします。

主成分分析は各データの分散が最大となるような射影軸を探索していきます。
やり方としては、分散共分散行列を計算し、固有値問題を解きます。

また、削減後の各次元が削減前の各次元をカバーできるかの割合を寄与率といいます。

## 実装
今回もirisデータセットを利用します。
やりたいこととしては、irisの説明変数は4つあるため、2次元に落とし込み図示をしてみます。

●コード

```python
# ライブラリのロード
from sklearn.datasets import load_iris
import pandas as pd
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
%matplotlib inline

# irisデータセットの取得
iris = load_iris()

# 次元数2とする
pca = PCA(n_components=2)
# 次元削減開始
iris_scaled = pca.fit_transform(iris.data)
# 寄与率(1次元め、2次元め)
print('寄与率:' + str(pca.explained_variance_ratio_))

# 散布図にプロット
iris_for_plot = np.hstack([np.array(iris_scaled), iris.target.reshape(-1,1)])
category1 = iris_for_plot[np.where(iris_for_plot[:,2] == 0)]
category2 = iris_for_plot[np.where(iris_for_plot[:,2] == 1)]
category3 = iris_for_plot[np.where(iris_for_plot[:,2] == 2)]
plt.scatter(x=category1[:,0], y=category1[:,1], marker='o') 
plt.scatter(x=category2[:,0], y=category2[:,1], marker='^') 
plt.scatter(x=category3[:,0], y=category3[:,1], marker='x')
```

●実行結果
実行ログは以下の通りで、第一主成分で9割ほどカバーできていることがわかります。

```
寄与率:[0.92461872 0.05306648]
```

また、プロットした結果は以下の通りで概ね2次元であまり情報のロスなく次元削減が出来た模様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618469/fca3f3ad-12cf-b58b-c4ed-ae56534e2b64.png)

# 最近傍・K近傍アルゴリズム
最近傍・k近傍アルゴリズムは教師あり学習の一種でロジスティック回帰モデルと同じく分類問題に対するアルゴリズムとなります。
コンセプトとしてはシンプルで、以下の手順で求まります。以下の手順2.で最上位のみを抽出する方法を最近傍アルゴリズムといい、上位k番目まで抽出する方法をk近傍アルゴリズムといいます。

1. 入力データと各学習データの距離を求める
2. 学習データを入力データと距離が近い順で並べ上位k番目まで抽出する
3. 抽出した学習データで最頻度のクラスを入力データのクラスと分類する

一般的にkの値を大きくするとクラス間を分ける決定境界が滑らかになります。
## 実装
今回もirisデータセットを利用します。
やりたいこととしては、irisデータセットのレコード数は150個ありますが、149個を学習データに使用、残り1個を未知の予測データとして、最近傍、3近傍で結果を見ていきます。
結果は見ての通り、最近傍、3近傍、実際のラベル共に一致しています。

●コード

```python
# 各種ライブラリのインポート
from sklearn.datasets import load_iris
import numpy as np

# irisデータセット取得
iris = load_iris()

# 学習データ
train_data = iris.data[:,][0:148]
# 入力データ
target_data = iris.data[:,][149]

# 入力データから近い順でランキング生成
ranking = np.argsort([np.linalg.norm(data - target_data) for data in train_data])
# 最近傍での判定
print("最近傍での判定:%s" %(iris.target_names[iris.target[ranking[0]]]))
# 3近傍での判定
# 上位3位のラベルの中で最頻度のラベルを求める
unique, freq = np.unique(iris.target[ranking[0:3]], return_counts=True)
mode = unique[np.argmax(freq)]
print("3近傍での判定:%s" %(iris.target_names[mode]))
# 実際の値
print("実際の値：%s" %(iris.target_names[iris.target[149]]))
```

●実行結果

```
最近傍での判定:virginica
3近傍での判定:virginica
実際の値：virginica
```

# k-means
k-meansとは教師なし学習の一つで、k個のクラスター(データのかたまり)に分類するためのアルゴリズムです。
アルゴリズムとしては、以下の通りです。

1. k個のクラスターの中心点を初期的に生成して、
2. 学習データの各点に対して、最も近いクラスターの中心点をラベリングし、
3. 各クラスターの中心点の重心を求め
4. 重心の値が収束するまで2,3を繰り返します。

アルゴリズムを見ての通り、手順1の初期値依存するため、適切なクラスタリングが出来ない場合があります。
この問題に対応するべく、k-means++があり、各クラスターの中心点の初期値をある程度離れさせる様にしています。

## 実装
今回もirisデータセットを利用します。
irisでは3つのラベリングがあるため、k-meansで3つクラスタリングを行い、どのくらい一致しているか確認してみます。

●コード

```python
# 各種ライブラリのインポート
from sklearn.datasets import load_iris
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import cluster, preprocessing, datasets
from sklearn.cluster import KMeans

# irisデータセット取得
iris = load_iris()

# kmeansモデル作成
model = KMeans(n_clusters=3)

# label取得
labels = model.fit_predict(iris.data)

print(labels)

print(iris.target)

```

●実行結果
一つの目の配列はクラスタリングによって分類した結果、二つ目の配列は実際の値です。
ラベルの値に差分がありますが、概ねクラスタリングが出来ております。

```
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 2, 2, 0, 2, 2, 2,
       2, 2, 2, 0, 0, 2, 2, 2, 2, 0, 2, 0, 2, 0, 2, 2, 0, 0, 2, 2, 2, 2,
       2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 0], dtype=int32)

array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
```


# サポートベクターマシン
サポートベクターマシンは教師あり学習の一つで、分類タスクで使用される機械学習モデルとなります。
サポートベクターマシンはクラス間で分類できる場合、分類する境界が最大となるように計算を行い、分類境界を使用して、クラスを分類するアルゴリズムとなります。

## 実装
今回もirisデータセットを利用します。
sklearnで手軽にSVMを利用出来、精度は97%が出ました。

●コード

```python
# 各種ライブラリのインポート
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
import numpy as np
from sklearn.svm import SVC 
from sklearn.model_selection import train_test_split

# irisのデータセット取得
iris = load_iris()

# 学習データと検証データを分割
X_train, X_test, Y_train, Y_test = train_test_split(iris.data, iris.target)
# SVMモデルを作成
model = SVC()
# SVMモデルを学習させる
model.fit(X_train, Y_train)
# 予測
pred = model.predict(X_test)
# 精度を算出
accuracy_score(Y_test, pred)
```

●実行結果

```
0.9736842105263158
```