教師あり学習：分類 -2次元入力3クラス分類-

## 2次元入力3クラス分類
2つの入力データに対して、3つのクラスに分ける。

```plantuml
@startuml
left to right direction

rectangle 入力 {
    usecase x0
    usecase x1
    usecase "x2(ダミー=1)" as x2
}
rectangle 入力総和 {
  usecase a0
}
rectangle 出力 {
  usecase "確率を求める近似値 y0" as f1
}

x0 --> a0
x1 --> a0
x2 --> a0

a0 --> f1
@enduml
```

```plantuml
@startuml
left to right direction

rectangle 入力 {
    usecase x0
    usecase x1
    usecase "x2(ダミー=1)" as x2
}
rectangle 入力総和 {
  usecase a1
}
rectangle 出力 {
  usecase "確率を求める近似値 y1" as f2
}

x0 --> a1
x1 --> a1
x2 --> a1

a1 --> f2
@enduml
```

```plantuml
@startuml
left to right direction

rectangle 入力 {
    usecase x0
    usecase x1
    usecase "x2(ダミー=1)" as x2
}
rectangle 入力総和 {
  usecase a2
}
rectangle 出力 {
  usecase "確率を求める近似値 y2" as f3
}

x0 --> a2
x1 --> a2
x2 --> a2

a2 --> f3
@enduml
```

2次元入力とダミーデータを含めた入力値3つに対して、y0, y1, y2に分類する。
y0, y1, y2に対応する入力総和a1, a2, a3について考える。

```math
a_{k} = w_{k0} x_{0} + w_{k1} x_{1} + w_{k2} x_{2}　(k = 0, 1, 2　ただしx_{2}=1 はダミーデータ)
```

これを和記号を使って表すと、
```math
a_{k} = w_{k0} x_{0} + w_{k1} x_{1} + w_{k2} x_{2} = \sum_{i=0}^{D}w_{ki}x_{i}
```
となる。

## ロジスティック回帰モデル
ロジスティック回帰モデルについて考える際は、シグモイド関数に入力総和の式を代入してきた。
2次元入力3クラス分類になると、シグモイド関数では出力要素が足りないため、拡張した**ソフトマックス関数**を使用する。
※今までは2クラス分類だったため、yと1-yの2つの出力でよかった。

ソフトマックス関数には多要素の入力値に対して大小関係が変わらないようして和が1になるよう複数の出力をする特性がある。
まずは入力総和をソフトマックス関数に入力するため、入力総和の指数関数を考え、すべての和をuとする。

```math
u = e^{a_{0}} + e^{a_{1}} + e^{a_{2}} =  \sum_{k=0}^{K-1} e^{a_{k}}
```

Kは分類するクラス数のためK=3としている。
uは自然対数の和であるため、0でない正の数である。
両辺をuで割ると、

```math
1 = \frac{e^{a_{0}}}{u} + \frac{e^{a_{1}}}{u} + \frac{e^{a_{2}}}{u}
```

このため、出力されるyの値は
```math
y_{k} = \frac{e^{a_{k}}}{u}　(k=0, 1, 2)
```
になる。このとき、
```math
y_{0} + y_{1} + y_{2} = 1
```
となり、確率の総和が1になる性質は保証される。

## 交差エントロピー誤差
交差エントロピー誤差を求めていく。

最尤推定は各確率の積になるので、
```math
P(t|x) = y_{0}^{t_{0}} y_{1}^{t_{1}} y_{2}^{t_{2}}
```
となる。

尤度はN個のデータが生成された場合にすべてのデータをかけるので、
```math
P(T|X) = \prod_{n=0}^{N-1} P(t_{n}|x_{n}) = \prod_{n=0}^{N-1}  y_{n0}^{t_{n0}} y_{n1}^{t_{n1}} y_{n2}^{t_{n2}} = \prod_{n=0}^{N-1} \prod_{k=0}^{K-1} y_{nk}^{t_{nk}}
```
となる。

平均エントロピー誤差は尤度の負の対数の平均なので、
```math
E(W) = - \frac{1}{N} \log P(T|X) = - \frac{1}{N} \log \prod_{n=0}^{N-1} P(t_{n}|x_{n}) = - \frac{1}{N} \log \prod_{n=0}^{N-1} \prod_{k=0}^{K-1} y_{nk}^{t_{nk}} = - \frac{1}{N} \log \sum_{n=0}^{N-1} \sum_{k=0}^{K-1} t_{nk} \log y_{nk}
```

## 決定境界線
平均エントロピー誤差を偏微分し、最小となる値を求めることで決定境界線を決める。
この際、平均エントロピー誤差を偏微分は、2次元入力2クラス分類で使ったシグモイド関数と同様になる。
※3クラス分類ではシグモイド関数でなく、ソフトマックス関数を使ったが偏微分した式は同じになるため。

```math
\frac{δE}{δw_{ki}} = \frac{1}{N} \sum_{n=0}^{N-1} (y_{nk} - t_{nk})x_{nk}
```

これをminimize()に渡してパラメータを求めると以下のように決定境界線が求められる。

```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.optimize import minimize

np.random.seed(seed=1)
N = 100
K = 3
T3 = np.zeros((N, 3), dtype=np.uint8)
T2 = np.zeros((N, 2), dtype=np.uint8)
X = np.zeros((N, 2))
X_range0 = [-3, 3]
X_range1 = [-3, 3]
Mu = np.array([[-.5, -.5], [.5, 1.0], [1, -.5]])
Sig = np.array([[.7, .7], [.8, .3], [.3, .8]])
Pi = np.array([0.4, 0.8, 1])
for n in range(N):
    wk = np.random.rand()
    for k in range(K):
        if wk < Pi[k]:
            T3[n, k] = 1
            break
    for k in range(2):
        X[n, k] = (np.random.randn() * Sig[T3[n, :] == 1, k] + Mu[T3[n, :] == 1, k])

T2[:, 0] = T3[:, 0]
T2[:, 1] = T3[:, 1] | T3[:, 2]

def show_data2(x, t):
    wk, K = t.shape
    c = [[.5, .5, .5], [1, 1, 1], [0, 0, 0]]
    for k in range(K):
        plt.plot(x[t[:, k] == 1, 0], x[t[:, k] == 1, 1], linestyle='none', markeredgecolor='black', marker='o', color=c[k], alpha=0.8)
    plt.grid(True)

# 3クラス用ロジスティック回帰モデル
def logistic3(x0, x1, w):
    K = 3
    w = w.reshape((3, 3))
    n = len(x1)
    y = np.zeros((n, K))
    for k in range(K):
        y[:, k] = np.exp(w[k, 0] * x0 + w[k, 1] * x1 + w[k, 2])
    wk = np.sum(y, axis=1)
    wk= y.T / wk
    y = wk.T
    return y

# 交差エントロピー誤差
def cee_logistic3(w, x, t):
    X_n = x.shape[0]
    y = logistic3(x[:, 0], x[:, 1], w)
    cee = 0
    N, K = y.shape
    for n in range(N):
        for k in range(K):
            cee = cee - (t[n, k] * np.log(y[n, k]))
    cee = cee / X_n
    return cee

# 交差エントロピー誤差の微分
def dcee_logistic3(w, x, t):
    X_n = x.shape[0]
    y = logistic3(x[:, 0], x[:, 1], w)
    dcee = np.zeros((3, 3))
    N, K = y.shape
    for n in range(N):
        for k in range(K):
            dcee[k, :] = dcee[k, :] - (t[n, k] - y[n, k]) * np.r_[x[n, :], 1]
    dcee = dcee / X_n
    return dcee

# パラメータサーチ
def fit_logistic3(w_init, x, t):
    res = minimize(cee_logistic3, w_init, args=(x, t), jac= dcee_logistic3, method="CG")
    return res.x

# モデル等高線2D表示
def show_contour_logistic3(w):
    xn = 30
    x0 = np.linspace(X_range0[0], X_range0[1], xn)
    x1 = np.linspace(X_range1[0], X_range1[1], xn)
    xx0, xx1 = np.meshgrid(x0, x1)
    y = np.zeros((xn, xn, 3))

    for i in range(xn):
        wk = logistic3(xx0[:, i], xx1[:, 1], w)
        for j in range(3):
            y[:, i, j] = wk[:, j]
    for j in range(3):
        cont = plt.contour(xx0, xx1, y[:, :, j], levels(0.5, 0.9), colors=['cornflowerblue', 'k'])
        cont.clabel('fmt=%1.1f', fontsize=9)
    plt.grid(True)

W_init = np.zeros((3, 3))
W = fit_logistic3(W_init, X, T3)
print(np.round(W.reshape((3, 3)), 2))
cee = cee_logistic3(W, X, T3)
print("CEE = {0:.2f}".format(cee))

plt.figure(figsize=(3, 3))
show_data2(X, T3)
show_contour_logistic3(W)
plt.show()
```
このコードでエラーが発生し、グラフは出せなかった。
```
PS C:\work\開発\repository\MachineLearning\devlop\06_classification\2D_3class> py .\plot_logitic3.py
C:\work\開発\repository\MachineLearning\devlop\06_classification\2D_3class\plot_logitic3.py:24: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  X[n, k] = (np.random.randn() * Sig[T3[n, :] == 1, k] + Mu[T3[n, :] == 1, k])
Traceback (most recent call last):
  File "C:\work\開発\repository\MachineLearning\devlop\06_classification\2D_3class\plot_logitic3.py", line 96, in <module>
    W = fit_logistic3(W_init, X, T3)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\work\開発\repository\MachineLearning\devlop\06_classification\2D_3class\plot_logitic3.py", line 75, in fit_logistic3
    res = minimize(cee_logistic3, w_init, args=(x, t), jac= dcee_logistic3, method="CG")
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\hi344\AppData\Local\Programs\Python\Python312\Lib\site-packages\scipy\optimize\_minimize.py", line 550, in minimize
    raise ValueError("'x0' must only have one dimension.")
ValueError: 'x0' must only have one dimension.
```