常態損失函數

      在〈常態損失函數〉中尚無留言

meshgrid

mesh[mɛʃ] 篩孔, 網線

numpy 的 meshgrid 是一個很好用的功能,可以產生每列 x 軸的座標及每行 y 軸的座標。

import numpy as np
a = range(5)
b = range(5)
x,y = np.meshgrid(a, b)
print("每列 x 的座標")
print(x)
print("每列 y 的座標")
print(y)
結果:
每列 x 的座標
[[0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]]
每列 y 的座標
[[0 0 0 0 0]
 [1 1 1 1 1]
 [2 2 2 2 2]
 [3 3 3 3 3]
 [4 4 4 4 4]]

上述不論是 x 軸或 y 軸,都是[1,n] 的二維陣列。

標示座標

如果要標示每行每列的座標,代碼如下

import numpy as np
a = range(5)
b = range(5)
x,y = np.meshgrid(a, b)
for row in zip(x, y):
    for col in  zip(row[0], row[1]):
        print(col, end='')
    print()
結果:
(0, 0)(1, 0)(2, 0)(3, 0)(4, 0)
(0, 1)(1, 1)(2, 1)(3, 1)(4, 1)
(0, 2)(1, 2)(2, 2)(3, 2)(4, 2)
(0, 3)(1, 3)(2, 3)(3, 3)(4, 3)
(0, 4)(1, 4)(2, 4)(3, 4)(4, 4)

傳統思維產生格點

如果要在二維平面上產生格點如下圖

傳統思維的代碼如下

import pylab as plt
for x in range(20):
    for y in range(20):
        plt.scatter(x,y, s=0.5, c='b')
plt.show()

plt.scatter

plt.scatter(x, y) 其中的 x 是 x 軸的集合,y 是 y 軸的集合,代碼如下

import pylab as plt
x=[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
y=[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4]
plt.scatter(x,y,c='b')
plt.show()

上面藍色的 x, y 值,光 5*5 就打到快骨折,那麼如果是 20*20 的話,不就沒完沒了

meshgrid 產生 x , y 集合

把 meshgrid 產生的 x, y,由 [1,n] 的二維陣列轉成一維陣列 (x.reshape(-1)),再放入 plt.scatter(x, y) 就完成了。

import numpy as np
import pylab as plt
a = range(5)
b = range(5)
x, y = np.meshgrid(a, b)
x = x.reshape(-1)
y = y.reshape(-1)
print(x)
print(y)
plt.scatter(x, y, s=0.5)
plt.show()
結果:
[0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4]
[0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4]

plt 自動將二維轉成一維

上述的 x=x.reshape(-1) 其實是多餘的,因為 plt 會自動將多維扁平化成一維,所以只需如下代碼即可。

import numpy as np
import pylab as plt
a = range(5)
b = range(5)
x,y = np.meshgrid(a, b)
plt.scatter(x,y, s=0.5)
plt.show()

總結

meshgrid 的主要目的並不是要產生 (x, y) 的配對,而是要產生 x 軸的 list 及 y 軸的 list,方便傳給 plt.plot()

BGD 損失率圖解

BGD 是Bitch Gradient Descent (批次梯度下降) 的縮寫。

底下是產生迴歸線資料 Regression.py 的代碼

#pip install matplotlib
import numpy as np
np.random.seed(1)
def getData(n):
    x = np.arange(-5, 5.1, 10 / n)
    y = 3 * x + 2 + (np.random.rand(len(x)) - 0.5) * 20
    return x, y

#取得等高線
def getContour(x, y):
    a=np.arange(-10,16,1)
    b=np.arange(-10,16,1)
    mesh=np.meshgrid(a,b)
    loss=np.zeros([len(a),len(b)])
    for p_x, p_y in zip(x, y):
        loss+=((mesh[0]*p_x+mesh[1])-p_y)**2
    loss/=len(x)
    return mesh,loss

BDG的損失函數梯度下降後,
$(\frac{\sigma Loss(a,b)}{\sigma a}=\frac{2}{n}\sum_{i=1}^{n}(ax_{i}+b-\tilde{y_{i}})*x_{i})$
$(\frac{\sigma Loss(a,b)}{\sigma b}=\frac{2}{n}\sum_{i=1}^{n}(ax_{i}+b-\tilde{y_{i}}))$

底下是BDG 類別 BDG.py 的代碼

import numpy as np
class BGD():
    def __init__(self, a, b, x, y, lr):
        self.a = a
        self.b = b
        self.x = x
        self.y = y
        self.lr = lr
        self.a_old = a
        self.b_old = b
        self.loss = None;

    def gradient(self):
        #偏微分時,如果有10億筆資料,底下就要加總10億次,會很慢
        #np.mean 跟 np.sum 一樣慢,把所有資料加總起來,再求平均數。
        grad_a = 2 * np.mean((self.a * self.x + self.b - self.y) * (self.x))
        grad_b = 2 * np.mean((self.a * self.x + self.b - self.y))
        return grad_a, grad_b
    def update(self):
        # 計算梯度
        grad_a, grad_b = self.gradient()
        # 梯度更新
        self.a_old = self.a
        self.b_old = self.b
        self.a = self.a - self.lr * grad_a
        self.b = self.b - self.lr * grad_b
        loss = ((self.a * self.x + self.b) - self.y) ** 2
        self.loss = np.mean(loss)

底下是主程式的代碼

#BGD : Batch Gradient Descent : 批量梯度下降
import pylab as plt
from BGD import BGD
from Regression import *
np.random.seed(1)
epoch=50
x,y = getData(100)
mesh, contour=getContour(x,y)
fig,ax=plt.subplots(nrows=1, ncols=2, figsize=(12,4))
a2=ax[1].contourf(mesh[0], mesh[1], contour, 15, cmap=plt.cm.Purples)
ax[1].set_xlabel("a")
ax[1].set_ylabel("b")
plt.colorbar(a2, ax=ax[1])
lr = 0.1
init_a=-9#初始值
init_b=-9
ax[1].scatter(init_a, init_b, c='g')
gd=BGD(init_a, init_b, x, y, lr)
for i in range(epoch):
    gd.update()
    ax[0].clear()
    ax[0].set_xlim(-5, 5)
    ax[0].set_ylim(-30,30)
    ax[0].scatter(x, y)
    ax[0].plot([x[0], x[-1]], [gd.a*x[0]+gd.b, gd.a*x[-1]+gd.b], c="orange")
    ax[0].set_title(f'{gd.a:.6f}x+{gd.b:.6f}')

    ax[1].set_xlim(-10, 15)
    ax[1].set_ylim(-10, 15)
    ax[1].set_title(f'iter:{i+1:03d}, loss={gd.loss:.6f}')
    ax[1].plot([gd.a_old, gd.a],[gd.b_old,gd.b],c='r')

    ax[1].scatter(gd.a, gd.b, c='g')

    plt.pause(0.01)
plt.show()

SGD 隨機梯度下降

SGD 為 Stochastic [stəˈkæstɪk] Gradient Descent 的縮寫。

BDG 損失函數梯度下降後,公式為$(\frac{\sigma Loss(a,b)}{\sigma a}=\frac{2}{n}\sum_{i=1}^{n}(ax_{i}+b-\tilde{y_{i}})*x_{i})$,必需將所有的 $(x_{i})$ 及 $(y_{i})$全都計算一遍。如果 i 只有幾百或幾千個,那電腦還可應付,但如果有上百萬,上億個點呢,這下就吃不消了。

SGD 就是把 $(\sum)$ 拿掉,然後使用亂數隨便取一個 x 及 y 來計算,這樣原本要加總計算上億個值,變成只要計算一次,就快很多了。當然啦,天下沒有白吃的午餐,如下圖所示,會呈現極不穩定的狀態。

SGD類別如下

from BGD import BGD
import numpy as np
class SGD(BGD):
    def __init__(self, a, b, x, y, lr):
        super().__init__(a, b, x, y, lr)
    def gradient(self):
        # 隨機取一筆資料進行計算
        idx = np.random.randint(len(self.x))
        grad_a = 2 * (self.a * self.x[idx] + self.b - self.y[idx]) * (self.x[idx])
        grad_b = 2 * (self.a * self.x[idx] + self.b - self.y[idx])
        return grad_a, grad_b

主程式如下

from Regression import *
import pylab as plt
from SGD import SGD
epoch=40
x,y=getData(100)
mesh, contour=getContour(x,y)
fig, ax=plt.subplots(nrows=1, ncols=2, figsize=(12,4))

a2=ax[1].contourf(mesh[0], mesh[1], contour,15, cmap=plt.cm.Purples)
plt.colorbar(a2,ax=ax[1])

lr = 0.01
init_a = -9; init_b = -9
ax[1].scatter(init_a, init_b, c='g')
gd = SGD(init_a, init_b, x, y, lr)
for i in range(epoch):
    gd.update()
    ax[0].clear()
    ax[0].set_xlim(-5,5)
    ax[0].set_ylim(-30, 30)
    ax[0].scatter(x, y)
    ax[0].plot([x[0], x[-1]],[gd.a*x[0]+gd.b, gd.a*x[-1]+gd.b], c="orange")
    ax[0].set_title(f'{gd.a:.6f}x+{gd.b:.6f}')

    print('iter=' + str(i) + ', loss=' + '{:.2f}'.format(gd.loss))
    ax[1].set_xlim(-10,15)
    ax[1].set_ylim(-10, 15)
    ax[1].set_title(f'iter:{i:03d} Loss: {gd.loss:6f}')
    ax[1].plot([gd.a_old, gd.a], [gd.b_old, gd.b], c='r')
    ax[1].scatter(gd.a, gd.b, c='g')
    ax[1].set_xlabel("a")
    ax[1].set_ylabel("b")
    plt.pause(0.01)
plt.show()

MBGD 小批量梯度下降

MBGD為 mini-batch gradient descent的縮寫。

因為SGD隨便選一個點來計算實在是不準確,所以就隨便選個100個點,或1000個點來計算。由底下的圖式可看出,確實平穩了許多,速度也快很多。MBGD 計算損失時,會產生 np.nan,所以如果遇到 np.nan 時,就中斷。

SGD 實在是上不了台面的,所以一般人家講的 SGD,其實是指 MBGD。

MBGD類別如下

from BGD import BGD
import numpy as np
class MBGD(BGD):
    def __init__(self, a, b, x, y, lr, batch_size):
        super().__init__(a, b, x, y, lr)
        self.batch_size=batch_size
        self.suffle=np.random.permutation(len(x))
        self.refresh()
        self.update_batch()
    def refresh(self):
        self.suffle=np.random.permutation(len(self.x))
        self.start=0
    def update_batch(self):
        idx = self.suffle[self.start:self.start + self.batch_size]
        self.start+=self.batch_size
        self.x_batch = self.x[idx]
        self.y_batch = self.y[idx]
    def gradient(self):
        grad_a = 2 * np.mean((self.a * self.x_batch + self.b - self.y_batch) * (self.x_batch))
        grad_b = 2 * np.mean((self.a * self.x_batch + self.b - self.y_batch))

#底下為提取批量到結尾,需重新打亂再重頭提取,避免 loss 為 nan
if self.start>len(self.x): self.refresh()
self.update_batch()
return grad_a, grad_b

主程式如下

from MBGD import MBGD
from Regression import *
import pylab as plt
epoch=200
x,y=getData(100)
mesh, contour=getContour(x,y)
fig, ax=plt.subplots(nrows=1, ncols=2, figsize=(12,4))

a2=ax[1].contourf(mesh[0], mesh[1], contour, 15, cmap=plt.cm.Purples)
plt.colorbar(a2,ax=ax[1])

lr=1e-2
init_a = -9; init_b = -9
ax[1].scatter(init_a, init_b, c='g')
batch_size=25
gd = MBGD(init_a, init_b, x, y, lr, batch_size)
for i in range(epoch):
    gd.update()
    ax[0].clear()
    ax[0].set_xlim(-5,5)
    ax[0].set_ylim(-30, 30)
    ax[0].scatter(x, y)
    ax[0].plot([x[0], x[-1]],[gd.a*x[0]+gd.b, gd.a*x[-1]+gd.b], c="orange")
    ax[0].set_title(f'{gd.a:.6f}x+{gd.b:.6f}')

    print('iter=' + str(i) + ', loss=' + '{:.2f}'.format(gd.loss))
    ax[1].set_xlim(-10,15)
    ax[1].set_ylim(-10, 15)
    ax[1].set_title(f'iter:{i+1:03d} Loss: {gd.loss:6f}')
    ax[1].plot([gd.a_old, gd.a], [gd.b_old, gd.b], c='r')
    ax[1].scatter(gd.a, gd.b, c='g')
    ax[1].set_xlabel("a")
    ax[1].set_ylabel("b")
    plt.pause(0.01)
plt.show()

SGD + 動量

上述的 MBGD 很容易卡在鞍點,所以可以再加入動量。本例應該是 MBGDM,但大家都稱 MBGD 為 SGD,所以本例命名為 SGDM

SGDM類別如下

from MBGD import MBGD
import numpy as np
class SGDM(MBGD):
    def __init__(self, a, b, x, y, lr, batch_size, gamma):
        super().__init__(a, b, x, y, lr, batch_size)
        self.gamma=gamma
        self.ma=0
        self.mb=0
    def update(self):
        grad_a, grad_b = self.gradient()
        self.a_old = self.a
        self.b_old = self.b
        self.ma = self.gamma * self.ma + self.lr * grad_a
        self.mb = self.gamma * self.mb + self.lr * grad_b
        self.a -=self.ma
        self.b -=self.mb
        loss = ((self.a * self.x + self.b) - self.y) ** 2
        self.loss = np.mean(loss)

主程式如下

from SGDM import SGDM
from Regression import *
import pylab as plt
epoch=40
x,y=getData(100)
mesh, contour=getContour(x,y)
fig, ax=plt.subplots(nrows=1, ncols=2, figsize=(12,4))

a2=ax[1].contourf(mesh[0], mesh[1], contour, 15, cmap=plt.cm.Purples)
plt.colorbar(a2,ax=ax[1])

lr = 0.01
init_a = -9; init_b = -9
ax[1].scatter(init_a, init_b, c='g')
batch_size=5
gamma=0.8
gd = SGDM(init_a, init_b, x, y, lr, batch_size, gamma)
for i in range(epoch):
    gd.update()
    ax[0].clear()
    ax[0].set_xlim(-5,5)
    ax[0].set_ylim(-30, 30)
    ax[0].scatter(x, y)
    ax[0].plot([x[0], x[-1]], [gd.a * x[0] + gd.b, gd.a * x[-1] + gd.b], c="orange")
    ax[0].set_title(f'{gd.a:.6f}x+{gd.b:.6f}')

    print('iter=' + str(i) + ', loss=' + '{:.2f}'.format(gd.loss))
    ax[1].set_xlim(-10,15)
    ax[1].set_ylim(-10, 15)
    ax[1].set_title(f'iter:{i+1:03d} Loss: {gd.loss:6f}')
    ax[1].plot([gd.a_old, gd.a], [gd.b_old, gd.b],  c= 'r')
    ax[1].scatter(gd.a, gd.b, c='g')
    ax[1].set_xlabel("a")
    ax[1].set_ylabel("b")
    plt.pause(0.01)
plt.show()

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *