機械学習は、データからそれらが生成されている法則性を学んで、予測や分類に役立てるものである。
ここでは、スタンフォード大学のAndrew Ng教授の講義ノートをベースに、機械学習の数学的バックグラウンドを整理する。
教師ありバイナリ学習のデータの表現
教師ありバイナリ学習に用いられるデータを、特徴量とターゲットのペアとして考える。これを、 により表現する。ここで、 は特徴量のベクトルを表し、 はターゲット変数を表す。
m個の学習用データの組は以下で表される
特徴量とターゲット変数をそれぞれまとめて以下のように記述する
ロジスティック回帰モデル
が与えられたとき、 以下の式によりを得たいとする。
(パラメータとアウトプット)
ロジスティック回帰モデルの損失関数
上記の定式化で、 が与えられたとき、を得たいとする。
損失(誤差)関数を以下のような「交差エントロピー誤差関数」として定義する。
最急降下法
上の式のを最小化するを見つけたいとする。例えばをの関数としてみた場合、の勾配を計算して、勾配が急な方向に沿って進む。つまり以下を反復計算する
計算グラフ
機械学習では以下のような計算グラフを考える。そして、その微分を逆伝搬で考える。これにより計算を局所化し、計算効率が高まる。
ここでは、を例として考える。
上のロジスティック回帰モデルの計算グラフとその微分(概要のみ)は以下のようになる。
m個のデータ例でのロジスティック回帰モデル
損失関数の式を再掲する
上記のように微分を逆伝搬で考える場合、各ノードの変数が1単位変化した時に最終出力変数がどのくらい変化するか、すなわち を考えることになる。例えば、についての勾配は、上の図の微分の逆伝搬を伝っていき、と求められる。いま、 のについての「勾配」を求めると、 となる。
また、 をについて解くと、 となり、逆関数の微分法より、 となる。それゆえ、 となる。
さらに、 より、 となる。同様に、, となる。
アルゴリズムのpythonによる実装
上記のアルゴリズムでロジスティック回帰の係数を求める計算をpythonで実装した。データはKaggle上にあるロジスティック回帰用のデータを用いた。ここでの留意点は、特徴量の線型結合の絶対値が大きくなる(数十程度)と、シグモイド関数が極値に振れてしまい、対数が計算できなくなることである。そのため、ここではあらかじめダミー変数以外の変数を平均0、標準偏差1で基準化した。
# ライブラリのインポート import numpy as np import pandas as pd import matplotlib.pyplot as plt import japanize_matplotlib # matplotlib日本語 # データの読み込み df01 = pd.read_csv('/content/drive/My Drive/Colab Notebooks/Social_Network_Ads.csv') # 性別に関するダミー変数の作成 df02 = pd.get_dummies(df01, drop_first=True, columns=['Gender']) # ダミー変数以外の特徴量を標準化する df02 = df02.assign(salary_std = (df02['EstimatedSalary']-df02['EstimatedSalary'].mean())/df02['EstimatedSalary'].std()) df02 = df02.assign(age_std = (df02['Age']-df02['Age'].mean())/df02['Age'].std()) # 分析に用いるデータをNumpy配列に書き出す features_list = ['salary_std','Gender_Male','age_std'] X_train = df02[features_list].to_numpy() y_train = df02['Purchased'].to_numpy() # バイナリ学習のアルゴリズム w1 = 1; w2 = 1; w3 = 1; b = 1 # 回帰係数の初期化 alpha = 1 # 学習率 m = len(y_train) z = np.zeros(m); a=np.zeros(m); dz=np.zeros(m) for j in range(250): # 指定した回数の反復計算 J = 0; dw1 = 0; dw2 = 0; dw3 = 0; db = 0 # 損失関数と勾配の初期化 for i in range(m): # データ行についての処理 z[i] = w1 * X_train[i,0] + w2 * X_train[i,1] + w3 * X_train[i,2] + b a[i] = 1 / (1+np.exp(-z[i])) J += -(y_train[i] * np.log(a[i]) + (1-y_train[i]) * np.log(1-a[i])) dz[i] = a[i] - y_train[i] dw1 += X_train[i,0] * dz[i] dw2 += X_train[i,1] * dz[i] dw3 += X_train[i,2] * dz[i] db += dz[i] J /= m; dw1 /= m; dw2 /= m; dw3 /= m; db /= m w1 -= alpha * dw1 w2 -= alpha * dw2 w3 -= alpha * dw3 b -= alpha * db if j % 20 == 0: print('j={}, J={:.3g}, w1={:.3g}, w2={:.3g}, w3={:.3g},b={:.3g}'.format(j,J,w1,w2,w3,b))
損失関数Jの収束の様子から学習率αを調節していく。αを調節した結果は以下のようになった。収束には100回以上の反復計算を要している。