機械学習入門(分類モデル)

schedule 2021/07/16  refresh 2023/11/09

  1.  
  2. 前回は回帰モデルについて説明しましたが今回は分類モデルについて説明しようと思います。回帰モデルはデータのプロットする点を通るのに最適な式を求めましたが今回はデータの特徴によりデータを分割するのに最適な境界の式を求めて境界の上はAグループ下はBグループなどと分類するイメージです。
  3.  
  4. 回帰モデルでは式の最適化に線形回帰の手法を使いましたが、分類モデルでは最も一般的なロジスティク回帰を使ってみます。主な違いは線形回帰では式の値を回帰しましたがロジスティク回帰では式の値を確率値化(0から1の範囲の値)してそれを回帰します、つまり確率を回帰する点です。
  5.  
  6. 分類モデル作成のステップ

  7. 1. 分類の境界関数を仮定する。

  8. 2. 確率関数化する。

  9. 3. 関数を学習データに最適化し関数の係数を決定する。


  10. 1. 分類の境界関数を仮定する。

     

    データ

    今回のデータは機械学習の学習用に公開されているデータiris datasetというがくの長さ、幅、花びらの長さ、幅とあやめの種類のデータを利用します。pythonの機械学習ツールパッケージのscikit-learnをインストールすればダウンロードしなくても読み込めるのでそれを使います。

境界関数


u = w1 * x1 + w2 * x2 + w3 * x3 + w4 * x4 + w5
u = W・X

 

上記のように仮定します。前回までの説明のように重みベクトルWとデータXの内積で表せます。

        1.  

2. 確率関数化する。

 

シグモイド関数

f(u) = 1 / (1 + e ** -u)

 

確率関数化するのにシグモイド関数を使います。eはネイピア数で高校の数学の教科書の後ろのほうに表が載っていた自然対数の底で2.7くらいの定数で円周率みたいなものと思ってもらっていいです。シグモイド関数の特徴は以下の通りでこれは境界関数をシグモイド関数にいれて確率値化しても矛盾が起きないことが分かると思います。

 

uが増えればf(u)が増える。

uが増えれば増えるほどf(u)は1に近づく。

uが減れば減るほどf(u)は0に近づく。

u=0のときf(u)=0.5。

u=0の点について点対称。

 


3. 関数を学習データに最適化し関数の係数を決定する。

 

尤度関数と最尤法


確率関数はパラメータを固定してデータを推定したりするが尤度関数はパラメータを変数としてそのもっともらしさを求める。最尤法は尤度関数で一番もっともらしいパラメータを求めるため尤度関数を微分した式が0になるようにパラメータを設定する方法です。この方法で最適化しようと思います。

 

尤度関数

u = W・X
f(u) = 1 / (1 + e ** -u)
yp = 1 / (1 + e ** -W・X)

P(yt, yp) = yp #yt = 1のとき
1 - yp #yt = 0のとき

ypがグループBの確率であることからグループAの確率は1 - ypとなるため正解率は上記の式になる。

L = P0 * P1 * P2...

データの確率はすべてのデータの確率の積なので上記の式になる。

ここでLを微分して0になるようにパラメータを設定します。

このまま微分したいところですが、データ数かけているのでパラメータがデータ数次式になってしまうこと。確率計算のプログラムを書くとよくあることですが極小の数字を扱うので0にまるめられてしまって計算ができなくなってしまうという問題があります。

 

対数尤度関数


log(L) = log(P0 * P1 * P2...)
       = log(P0) + log(P1) + log(P2)...

Lの対数関数log(L)を対数尤度関数といい、損失関数に使います。対数をとることでPの足し算になりパラメータの次数が増えないことと数値が大きくなり精密な計算機でなくても計算できるようになります。
 
P(yt, yp) = yt * yp + (1 - yt) * (1 - yp)
yt = 1のときyt = 0のときでは計算できないので上記のようにまとめます。
 
log(P0) = yt0 * log(yp0) + (1 - yt0) * log(1 - yp0)
 
例えばlog(P0)は上記のようになりデータ数分足したものが対数尤度関数になります。

 

損失関数


L = -1 / M (log(P0) + log(P1) + log(P2)... + log(P(M-1)))
log(P) = yt * log(yp) + (1 - yt) * log(1 - yp)

いつものようにデータ数に絶対値が左右されないようにデータ数Mでわります。損失関数なので最小値が確率最大になるように-1をかけてひっくり返して損失関数の出来上がりです。尤度関数Lを損失関数Lとして定義しなおしているので気をつけてください。

全体の微分の前に損失関数の-1 * log(P)の部分の微分はどうなるのか見ていきましょう。
 
z = -1 * (yt * log(yp) + (1 - yt) * log(1 - yp))
 
上記のようにzとして定義します。

dz / dyp = -1 * (yt / yp + (1 - yt) / (1 - yp) * -1)
         = -1 * ((yt * (1 - yp) + (yt - 1) * yp) / yp * (1 - yp))
         = -1 * ((yt - yt * yp + yt * yp - yp) / yp * (1 - yp))
         = -1 * ((yt - yp) / yp * (1 - yp))
         = (yp - yt) / yp * (1 - yp)

zをypで微分すると(yp - yt) / yp * (1 - yp)になります。
 
dL/dw1 = dL/dyp * dyp/du * du/dw1
       = (yp - yt) / yp(1 - yp) * yp * (1 - py) * x1
       = (yp - yt) * x1

損失関数Lは重みWを変数とみると関数u(W)とシグモイド関数yp(u)と損失関数L(yp)の合成関数なので例えばLをw1で微分すると上記のように変換できます。

yp - ytは誤差でありそれにデータの値x1を掛けた値です。何か見覚えがありませんか?そうです前回の機械学習入門(回帰モデルその2)で出てきた損失関数の式と全く同じになってしまいました。つまり長々と説明しましたが計算ロジックは全く同じものになります。

 

 

Pythonでの実装

 

データ生成


from sklearn import datasets
from sklearn.model_selection import train_test_split

iris = datasets.load_iris()
x = iris.data
y = iris.target
idx = np.where((y == 0) | (y == 1))
x = x[idx]
yt = y[idx]

train_x, test_x, train_y, test_y = train_test_split(x, yt, test_size=0.3)

scikit-learnのパッケージをインストールして同梱されているiris datesetを読み込んでいます。

idx = np.where((y == 0) | (y == 1)) 

はiris datasetに3種類のアヤメのデータが入っているので2種類のアヤメんのデータのインデックスをとってデータを抽出しています。

 

train_test_splitはデータをシャッフルして指定した割合で学習データとテストデータに分けてくれる関数です。

 

各関数


def pred(x, w):
    u = x @ w
    return sigmoid(u)

 

予測式です。境界式をシグモイド関数にいれてます。

 

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


シグモイド関数です。

 

def accuracy_rate(w, x, y):
    yp = pred(x, w)
    result = np.where(yp > 0.5, 1, 0)
    diff = (test_y == result)
    correct = diff[np.where(diff == True)]
    return (correct.size / diff.size) * 100

 

重みwとテストデータxでどちらのアヤメが推定してテストの正解yと答え合わせをして正解率を出してます。

残りは前回のコードと全く同じなので説明を割愛。

 

全体


import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split

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

def pred(x, w):
u = x @ w
return sigmoid(u)

def accuracy_rate(w, x, y):
yp = pred(x, w)
result = np.where(yp > 0.5, 1, 0)
diff = (test_y == result)
correct = diff[np.where(diff == True)]
return (correct.size / diff.size) * 100

iris = datasets.load_iris()
x = iris.data
y = iris.target
idx = np.where((y == 0) | (y == 1))
x = x[idx]
yt = y[idx]

train_x, test_x, train_y, test_y = train_test_split(x, yt, test_size=0.3)

# データ数
M = train_x.shape[0]
# データ次元数
D = train_x.shape[1]
w = np.ones(D)
l_rate = 0.01
max_run = 100
loss = 0

rate = accuracy_rate(w, test_x, test_y)
print(rate)

for _ in range(max_run):
yp = pred(train_x, w)
yd = yp - train_y
w -= l_rate * train_x.T @ yd / M
loss = np.mean(yd**2) / 2

rate = accuracy_rate(w, test_x, test_y)
print(rate)

 

> python test.py
> 56.666666666666664
> 100.0

 

学習前と後で正解率が上がっているのが分かりますね。

 

(2022/3/25 学習データがテストデータを含めたものを入れていたので修正)