SEワンタンの独学備忘録

IT関連の独学した内容や資格試験に対する取り組みの備忘録

【機械学習】入門⑩ 分類-勾配法による決定境界の算出- Pythonで学ぶ「教師あり学習」

ひさしぶりになりすぎたので、過去記事をちゃんと読み返してからの再開となりました。
↓↓前回

www.wantanblog.com


前回までの内容(復習)

私自身が内容を忘れているのでダイジェストで前回までの内容を復習。

問題提起

本ブログのGoogleアナリティクスのデータの中である期間のアクセス数と平日か休日(曜日)を抽出してデータの状態を図示した。

f:id:wantanBlog:20200316031310p:plain

別の期間のアクセス数から平日か休日かを分類するために今回の期間から平日か休日かを振り分ける境界を設定することにした(=決定境界)。

決定境界はどのように定めればいいだろうか?

確率表現と最尤推定

f:id:wantanBlog:20200319230421p:plain

決定境界によってデータの分類を行おうとしたときに、今回のデータ範囲において、必ず休日(1)に分類される領域と必ず平日(0)に分類される領域、そしてどちらも取りうる領域(クラスB)が存在する。

問題はクラスBの領域で、この領域にデータが存在するときに休日か平日のどちらに分類するかを確率で表現することにした。
アクセス数xに対して休日である確率は以下の式で表現する。

f:id:wantanBlog:20200319233251p:plain

確率での表現を行うにあたり、統計学の手法である最尤推定の考え方を用いることにした。

f:id:wantanBlog:20200320004005p:plain

クラスBの存在する8つのデータの結果となる、最もらしい確率wを算出しようとしている。

上記のような単純なケースで算出するまでもないが、最もらしい確率は「0.125」と算出することができた。

ロジスティック回帰と交差エントロピー誤差

今回のデータサンプルを確率表現(0~1の範囲)に落とし込むためにロジスティック回帰を適用した。
ロジスティック回帰モデルには以下のようなシグモイド関数を用いる。

f:id:wantanBlog:20200321235500p:plain

ロジスティック関数を用いてデータの生成確率を表現すると以下のようなモデルとなる。

f:id:wantanBlog:20200322005208p:plain

このとき、最もらしいパラメータw0とw1を求めるために交差エントロピー誤差の考え方を適用する。

交差エントロピー誤差を導出し、パラメータを求めるために「平均交差エントロピー誤差」を表現する式を導出した。
以下の式の最小となる点を求めることが最尤推定を行うことになる。

f:id:wantanBlog:20200322015155p:plain

平均交差エントロピー誤差の式をPythonにより実装し、グラフの概観を図示することにより、最小となる点のおおむねの値の予想はついた。

f:id:wantanBlog:20200322030303p:plain


勾配法によるパラメータの算出

勾配法の導入

前回までに導出した「平均交差エントロピー誤差」のグラフにより最小になるであろう点を与えるパラメータw0とw1の概ねの値を推測することはできますが、以下の示すシグモイド関数のように二つの変数(パラメータ)を含んでいるためそのままの形では正確な最小値を算出することはできません。

f:id:wantanBlog:20200418174009p:plain

それを解消するために「勾配法」を用いることにします。
勾配法は「回帰」で導入したものと考え方は同じです。

※参考
www.wantanblog.com

勾配法を用いるためには各パラメータに対して偏微分を行うのでした。
以下の「平均交差エントロピー誤差」の式を基に考えていきます。

f:id:wantanBlog:20200418175851p:plain

平均交差エントロピー誤差のWに対する偏微分

平均交差エントロピー誤差」E(W)に対して偏微分を行ます。
偏微分なので、w0とw1のそれぞれのパラメータに対して微分を行っていくことになります。

実際に偏微分を紐解いていきますが、やや難解です。概ねやっていることが理解できていれば結果のみ切り取って次に進んでもいいのではないかと思います。

【w0に対する偏微分】

f:id:wantanBlog:20200418180131p:plain


・式の置き換え

f:id:wantanBlog:20200418182839p:plain


f:id:wantanBlog:20200418182914p:plain

f:id:wantanBlog:20200418182955p:plain


・偏微分に対する連鎖律の適用
※1連鎖律の適用
f:id:wantanBlog:20200418183100p:plain

・右辺の偏微分

f:id:wantanBlog:20200418183205p:plain

※2シグモイド関数の微分公式適用
f:id:wantanBlog:20200418183826p:plain

f:id:wantanBlog:20200418184031p:plain

・右辺の整理

f:id:wantanBlog:20200418185140p:plain


※1連鎖律の適用
www.wantanblog.com


※2シグモイド関数の微分公式適用
www.wantanblog.com


【w1に対する偏微分】

以下部分以外w0とほぼ同様になりますので結果だけ。

f:id:wantanBlog:20200418185931p:plain

f:id:wantanBlog:20200418185943p:plain

Pythonでの実装

勾配法を適用の下準備として、上記で求め平均交差エントロピーを偏微分した式をPythonで実装してみます。

・実装例

# 平均誤差エントロピーの微分
import numpy as npy
import matplotlib.pyplot as plt
%matplotlib inline

# ロジスティック関数
def logistic(x,w):
    y = 1 / (1 + npy.exp(-(w[0]*x + w[1])))
    return y

# 偏微分後の式
def dcee_logistic(w,x,t):
    # ロジスティック関数を適用する
    y = logistic(x,w)
    # 返却値
    dcee = npy.zeros(2)
    # データの数だけループする
    for num in range(len(y)):
        # w0の偏微分式
        dcee[0] = dcee[0]+(y[num]- t[num])*x[num]
        # w1の偏微分式
        dcee[1] = dcee[1]+(y[num]- t[num])
    # 偏微分により算出した値の平均をとる
    dcee = dcee / X_n
    return dcee

# データセットの取り出し
sample_data = npy.load('classdata1.npz')
X_n = sample_data['X_n']
X = sample_data['X']
T = sample_data['Y']

W=[1,1]
result = dcee_logistic(W, X, T)

print(result)

・出力結果

[129.79310345 0.62068966]

この結果が何を表しているかというとw0=1、w1=1のときの勾配ベクトルを表しています。

f:id:wantanBlog:20200418214931p:plain

勾配法によりパラメータを求める

ついに核心にせまります。

Pythonによる実装

勾配ベクトルが求めることができましたので、これを使用してロジスティック関数モデルのパラメータを算出してみます。
先に実装内容は示します。

なおパラメータの導出にはscipy.optinizeminimize関数を用いています。

# 勾配法によるパラメータの算出
import numpy as npy
import matplotlib.pyplot as plt
from scipy.optimize import minimize
%matplotlib inline

# ロジスティック関数
def logistic(x,w):
    y = 1 / (1 + npy.exp(-(w[0]*x + w[1])))
    return y

# ロジスティック関数の描写
def show_logistic(w):
    xb = npy.linspace(X_min, X_max, 100)
    y = logistic(xb, w)
    plt.plot(xb,y,color='gray',linewidth=4)
    # 決定境界
    i = npy.min(npy.where(y > 0.5))
    B = (xb[i -1] + xb[i])/2
    plt.plot([B, B], [-.5, 1.5],color='k',linestyle='--')
    plt.grid(True)
    return B

# データ分布表示関数
def show_data(x,t):
    K = npy.max(t) + 1
    for k in range(K):
        plt.plot(x[t == k], t[t == k], X_col[k],alpha=0.5, linestyle='none',marker='o')
    plt.grid(True)
    plt.ylim(-.5, 1.5)
    plt.xlim(X_min, X_max)
    plt.yticks([0,1])

# 平均交差エントロピー誤差
def cee_logistic(w,x,t):
    y = logistic(x,w)
    cee = 0
    for n in range(len(y)):
        cee = cee -(t[n]*npy.log(y[n]) + (1 - t[n]) * npy.log(1 - y[n]))
    cee = cee / X_n
    return cee

# 偏微分後の式
def dcee_logistic(w,x,t):
    # ロジスティック関数を適用する
    y = logistic(x,w)
    # 返却値
    dcee = npy.zeros(2)
    # データの数だけループする
    for num in range(len(y)):
        # w0の偏微分式
        dcee[0] = dcee[0]+(y[num]- t[num])*x[num]
        # w1の偏微分式
        dcee[1] = dcee[1]+(y[num]- t[num])
    # 偏微分により算出した値の平均をとる
    dcee = dcee / X_n
    return dcee

def fit_logistic(w_init, x, t):
    result = minimize(cee_logistic, w_init, args=(x,t), jac=dcee_logistic,method="CG")
    return result.x

# データ生成
# データセットの取り出し
sample_data = npy.load('classdata1.npz')
X_min = sample_data['X_min']
X_max = sample_data['X_max']
X_n = sample_data['X_n']
X = sample_data['X']
T = sample_data['Y']
X_col = ['cornflowerblue','gray']

plt.figure(1,figsize=(3,3))
W_init = [0.14,-25]
W = fit_logistic(W_init, X, T)
print("w0={0:.2f},w1={1:.2f}".format(W[0],W[1]))

B=show_logistic(W)
show_data(X,T)
plt.ylim(-.5, 1.5)
plt.xlim(X_min, X_max)
cee = cee_logistic(W,X,T)
print("CEE={0:.2f}".format(cee))
print("Bounday={0:.2f} ".format(B))
plt.show()

・出力結果

[-0.07445384 11.88955659]
w0=-0.07,w1=11.89
CEE=0.22
Bounday=180.50

f:id:wantanBlog:20200418230743p:plain

実装内容について
W_init = [0.14,-25]

初期パラメータを定めています。
実際のデータ範囲によって調整します。前回のロジスティック回帰に用いたパラメータを用いるとうまくいきました。

def fit_logistic(w_init, x, t):
    result = minimize(cee_logistic, w_init, args=(x,t), jac=dcee_logistic,method="CG")
    return result.x

minimize」については、第一引数にロジスティック関数を指定します。
jac=dcee_logistic」に偏微分した関数を指定します。
method="CG"」で勾配法のタイプを指定します。
今回は共役勾配法を選択していますが詳細な内容までは把握していません。

戻り値の中からパラメータにあたるものを取得して返却します。
ここで取得できたパラメータを平均交差エントロピー誤差に当てはめることで、最小値が求められ今回の事象を表す最適なモデルとすることができます。

B=show_logistic(W)

算出したパラメータを適用したロジスティック関数を描写します。
また、求めたロジスティック関数の値が0.5にあたる部分を決定境界として値を算出しています。

cee = cee_logistic(W,X,T)
print("CEE={0:.2f}".format(cee))

偏微分から求めたパラメータを用いて平均交差エントロピー誤差を算出しています。

結果からわかること

・出力結果(再掲)

[-0.07445384 11.88955659]
w0=-0.07,w1=11.89
CEE=0.22
Bounday=180.50

f:id:wantanBlog:20200418230743p:plain

まず平均交差エントロピー誤差(CEE)について、誤差値なので他との比較となりますが前回パラメータを決め打ちして求めたときと比較してもかなり小さくなっていることがわかります。

・W=[0.14,-17](前回)の出力結果

8.018694918854194

またパラメータの値についても「w0=-0.07,w1=11.89」であり、以前作成した等高線の図から妥当であることが分かります。

決定境界は「180.05」と求められました。
これはつまり、アクセス数が180.05以下の場合には休日に分類し、それ以上の場合には平日に分類するのが最も妥当と言うことができます。

これで、一次元入力パラメータの場合にはおける目標としていた決定境界を求めることができました。


・今回のソース
python_dev/Classification3.ipynb at master · wantanblog/python_dev · GitHub