Python
最適化
確率的勾配降下法
最急降下法
勾配降下法

ロジスティック回帰で各種勾配降下法のアニメーション・可視化(python実装)


概要


  • ロジスティック回帰を例に、各種勾配降下法をコードレベルで理解


    • 最急降下法、確率的勾配降下法、ミニバッチ確率的勾配降下法



  • 各種勾配降下法を実装


    • 行列計算はnumpyを使用



  • 各種勾配降下法の挙動を可視化


1 最適化するロジスティック回帰の目的関数の導入

今回は、2クラス分類のロジスティック回帰を使用する。

(以下、2クラス分類のロジスティック回帰を、単にロジスティック回帰と呼ぶ。)

ロジスティック回帰の出力値は以下の式で表され、0から1の確率として得られる。

y^* = sigmoid(xw + b)\\

y^* : 予測値\\
x : 説明変数\\
w : 重み\\
b : バイアス\\
sigmoid(X) : Xを(0,1)区間に写像\\

例えば、2クラスA・Bを想定し、ロジスティック回帰の出力をクラスAである確率とすると、以下のようになる。

y^* : クラスAである確率\\

1-y^* : クラスBである確率(=クラスAではない確率)

そして、ロジスティック回帰では、予測値y*と正解ラベルyの差から計算される損失関数Lを最小化する回帰直線を求めることになる。この回帰直線を求める方法として勾配降下法がある。勾配降下法とは、損失関数から得られる勾配を使って、損失関数をより小さくする重みwとバイアスbを、逐次更新していく手法である。

詳細は省くが、プログラムで使用する勾配の計算部分パラメータ更新部分は以下の通りである。

(1)勾配の計算\\

\frac{∂L}{∂w} = - \sum_{n=1}^{N}x_{n}(y - y^*)\\
\frac{∂L}{∂b} = - \sum_{n=1}^{N}(y - y^*)\\

(2)パラメータ更新\\

w ← w - \eta\frac{∂L}{∂w}\\

b ← b - \eta\frac{∂L}{∂b}\\

ただし、ηは学習率と呼ばれ、一回の更新でどれくらいパラメータ(w, b)を変化させるかという度合いと表す。


2 各種勾配降下法について

今回は、以下の3種類の勾配降下法を試す。


  • 最急降下法

  • 確率的勾配降下法(SGD)

  • ミニバッチ確率的勾配降下法(MSGD(単にSGDと呼ばれることもある))

これらは、勾配を計算するときに使用するデータ数で区別することができる。

そして、確率的勾配降下法ミニバッチ確率的勾配降下法では、データをシャッフルする。こうすることで、上から順に1行ずつ処理するということが、ランダムにデータを抽出するということになる。

最急降下法
確率的勾配降下法
ミニバッチ確率的勾配降下法

使用するデータ数 / 勾配計算
全部
1つ
数個(ミニバッチ)

データの選択順
---
ランダム
ランダム


3 実装

まず、サンプルデータを準備する。

点(0, 0), (6, 6)の周りに、ランダムに50個ずつサンプル点を用意する。そして、片方(赤)の正解ラベルを「0」に、もう片方(青)を「1」にする。

import numpy as np

import matplotlib.pyplot as plt

d = 2
N = 100
x1 = np.random.randn(N//2, d)
x2 = np.random.randn(N//2, d) + np.array([6, 6])

x = np.vstack((x1, x2))

label1 = np.zeros(N//2)
label2 = np.ones(N//2)
label = np.hstack((label1, label2))

dataset = np.column_stack((x,label))
np.random.shuffle(dataset) #データ点の順番をシャッフル

x = dataset[:, :2]
label = dataset[:, 2]

plt.scatter(x1[:, 0], x1[:, 1], c='r')
plt.scatter(x2[:, 0], x2[:, 1], c='b')
plt.show()
plt.close()

次に、使用する関数を3つ定義する。


  • シグモイド関数

def sigmoid(a):

return 1.0 / (1.0 + np.exp(-a))


  • 現在のパラメータにおける、ロジスティック回帰の予測値を返す関数

def logistic(x):

return sigmoid(np.dot(x, w) + b)


  • 勾配を返す関数

def grad(x, label):

error = label - logistic(x) # (正解ラベルy) - (予測値y*)
w_grad = -np.mean(x.T * error, axis=1) # 式(1) 上に記載
b_grad = -np.mean(error) # 式(1)

return w_grad, b_grad

そして、パラメータを適当に初期化した後、各種勾配降下法によるパラメータの最適化を行う。ここで、今回は手法ごとにプログラムを書き分ける手間を省くため、「minibatch_size」の大きさで、手法を区別する。


  • 最急降下法(全部) : minibatch_size = N

  • 確率的勾配降下法(1つ) : minibatch_size = 1

  • ミニバッチ確率的勾配降下法(数個) : minibatch_size = 10

w = np.random.rand(d)

b = np.random.random()

eta = 0.1

# 1つ選択
# minibatch_size = N # 最急降下法
# minibatch_size = 1 # 確率的勾配降下法
minibatch_size = 10 # ミニバッチ確率的勾配降下法

# パラメータ更新毎の損失
loss_list = list()

for epoch in range(50):

for iteration, index in enumerate(range(0, x.shape[0], minibatch_size)):
_x = x[index:index + minibatch_size]
_label = label[index:index + minibatch_size]
w_grad, b_grad = grad(_x, _label)
w -= eta * w_grad
b -= eta * b_grad
loss_list.append(np.mean(np.abs(label - logistic(x))))

# 損失の確認
print(np.mean(np.abs(label - logistic(x))))
plt.plot(loss_list)
plt.show()
plt.close()

# 回帰直線の確認
bx = np.arange(-6, 10, 0.1)
by = -b/w[1] - w[0]/w[1]*bx
plt.xlim([-5, 10])
plt.ylim([-5, 9])
plt.plot(bx, by)
plt.scatter(x1[:, 0], x1[:, 1], c='r')
plt.scatter(x2[:, 0], x2[:, 1], c='b')
plt.show()
plt.close()


4 結果


最急降下法

output.gif

全データから勾配を計算するので、損失が大きくなることはない。(最短で、最適解 or 局所最適解に落ちる。)


確率的勾配降下法

output.gif

データ1つずつから勾配を計算するので、場合(ノイズの大きいデータ)によっては、損失が大きくなることもある。(解までは最短ではないが、局所最適解に落ちたとしても、抜け出して最適解に落ちる可能性がある。)


ミニバッチ確率的勾配降下法

minibatch_size = 10 のとき。

output.gif

データ数個ずつから勾配を計算するので、確率的勾配降下法よりは、損失がなまされて、ばらつきは小さい。


最後に

実装と可視化が目的だったので、結果以上の考察はしてません。各種勾配降下法のメリット・デメリットは以下をどうぞ。


参考