損失函數偏微分

      在〈損失函數偏微分〉中尚無留言

此篇難度頗高,有許多數學公式及微積分。本篇是進入 AI 模型的基礎,需多花點心思慢慢理解。

迴歸線定義

多個點中尋找一條線,此線與每個點的 y 軸距離平方總和為最小值,那此條線就稱為迴歸線。如下圖,所有紅色線的長度平方,加總後,若得到最小值,那麼那條線就是迴歸線。

上圖可由如下代碼所產生

import numpy as np
import pylab as plt
plt.figure(figsize=(9,6))
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False

#隨機產生20個散佈點
n=20
np.random.seed(42)
x=np.linspace(-10,10, n)
y=0.5*x+3-np.random.randint(-5,5, n)
plt.scatter(x, y)

#計算迴歸線
args=np.polyfit(x, y, 1)
f=np.poly1d(args)
plt.plot(x, f(x), color="green")
sum=0
for i in range(len(x)):
    sum+=(y[i]-f(x[i]))**2
    plt.plot([x[i], x[i]], [y[i], f(x[i])], c='red')
print(sum)
plt.xlim(-2,22)
plt.ylim(-1,15)
plt.show()

微分 – 導數

上圖藍色曲線的方程式,假設為 f(x),則(x, f(x))這個點的切線(紅色線)斜線為多少呢?。

首先x往右邊增加一個h的值,其y軸為f(x+h),所以綠色(割線)斜率為$(\frac{f(x+h)-f(x)}{h})$

當h往左邊靠,一直逼近x時,那就是紅色線的斜率 $(\lim_{h\to0}\frac{f(x+h)-f(x)}{h})$

把上面的那個式子,給予一個變數 $(f'(x)=\lim_{h\to0}\frac{f(x+h)-f(x)}{h})$, 則$(f'(x))$稱為導數
所以導數就是某個點的斜率。

假設$(f(x)=x^{2})$,則斜率為

$(\frac{f(x+h)-f(x)}{h}=\frac{(x+h)^{2}-x^{2}}{h}=\frac{x^{2}+2xh+h^{2}-x^{2}}{h}=2x+h)$

$(f'(x)=\lim_{h\to0}2x+h = 2x)$

也就是說,x=1時,斜率為2, x=2時,斜率為4

而$(f'(x)=2x)$ 正是 $(x^{2})$的微分,也就是說,微分就是在求切線,也就是微分後的函數

為什麼要微分

微分是在求切線斜率,比如 $(s=\frac{1}{2}at^2)$,那麼$(s’=at)$,$(s’)$ 這個切線斜率就是物理學的瞬間速度 $(s’=v=at)$

在 x 時,y 值為 f(x),那麼下一個點的 y 值是多少呢? 假設下一點的 x 多了一點點 t,那麼下一點的 y 值為 $(y_i = y_{i-1} + f'(x)*t)$,下一點的 y 值,是前一點的 y 值加上 $(f'(x) * t)$。請注意,$(t)$ 值不可以太大,否則會偏離曲線太多。但 $(t)$ 值如果太小,則前進的速度又太慢了。

怪怪的,為什麼是加上 $(f'(x) * t)$ 呢!! 以物理學來說 $(f'(x))$ 就是速度 $(v)$ ,$(v * t)$ 就是距離。

偏微分

上述的方程式,只有一元變數(x),但如果是多元多次方程式呢,如下所示

$(f(x,y) = 5x^2+2y^2+7xy+10x+y+1)$

這時此對 x 及 y 進行偏微分,表示式如下

$( \frac{\partial f(x,y)}{\partial x} = 10x + 7y + 10)$

$( \frac{\partial f(x,y)}{\partial y} = 4y+7x+1)$

ps. $(\partial)$ 有的人唸 partial(帕修),也有人唸delta,還有人唸Costco,但版主的年代,是唸ro(肉)。

損失函數

在上面的迴歸線中,規定 (實際值 – 預測值) 的平方總和必需為最小數,假設迴歸線是 $(r_i=ax_i+b)$,也就是

$(\sum_{1}^{n}(y_i-r_i)^2 = \sum_{1}^{n}(y_i-(ax_i+b))^2)$ 必需是最小值。

我們把 $(\sum_{1}^{n}(y_i-(ax_i+b))^2)$ 給定一個函數名稱 $(Loss(a,b))$,稱為損失函數

也就是損失函數為 $(Loss(a,b) = \sum_{1}^{n}(y_i-(ax_i+b))^2)$,而計算出來的值稱為損失值

一階回歸線偏微分

損失函數解開後的公式為
$(Loss(a,b)=\sum_{i=1}^{n}(y_{i}-(ax_{i}+b))^2=\sum_{i=1}^{n}y_{i}^2-2y_{i}ax_{i}-2y_{i}b+a^2x_{i}^2+2ax_{i}b+b^2)$

對 a 偏微分
$(\frac{\partial Loss(a,b)}{\partial a}=\sum_{i=1}^{n}(-2y_{i}x_{i}+2ax_{i}^2+2x_{i}b)=2\sum_{i=1}^{n}(-y_{i}+ax_{i}+b)x_{i}=2\sum_{i=1}^{n}(r_{i}-y_i)x_{i})$

對 b 偏微分
$(\frac{\partial Loss(a,b)}{\partial b}=\sum_{i=1}^{n}(-2y_{i}+2ax_{i}+2b)=2\sum_{i=1}^{n}(ax_{i}+b-y_{i})=2\sum_{i=1}^{n}(r_{i}-y_i))$

為什麼要對損失函數進行偏微分

損失函數的未知數為 a 及 b ,在 $(r_i= ax_i +b)$ 中,因為不知道 a,b 的值是多少,所以會先假設 a=0,b=0,計算損失值,然後用逼近法求取下一次的 a,b 值。

依逼近公式
$(a_{新值}=a_{前一次} – \frac{\partial Loss(a,b)}{\partial a} * lr)$
$(b_{新值}=b_{前一次} – \frac{\partial Loss(a,b)}{\partial b} * lr)$

取得新 a,b 值後重新計算最新損失值,如果最新損失值小於上一次的損失值,表示有成效,就可以再進行下一輪逼近。

這又怪怪的,為什麼是前一次 – 微分*lr,因為損失函數的值是愈來愈小,所以當然是用的。

迴歸線逼近代碼

如下的代碼中,應用了上述的公式及觀念,一步一步的逼近正確的迴歸線

import numpy as np
import pylab as plt
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False
n=20
np.random.seed(42)
x=np.linspace(-10,10, n)
y=0.5*x+3-np.random.randint(-5,5, n)
f=np.poly1d(np.polyfit(x, y, 1))
pre_loss=1_000_000_000
loss=100_000_000
lr=2.5e-3
a = 0
b = 0
fig=plt.figure(figsize=(9,6))
ax=fig.add_subplot()
while pre_loss-loss>0.001:
    ax.clear()
    ax.set_xlim(-12,12)
    ax.set_ylim(-6,10)
    ax.plot([0, 0], [-6, 10], color="blue", linewidth=0.5)
    ax.plot([-12, 12], [0, 0], color="blue", linewidth=0.5)

    plt.scatter(x, y, c='b')
    plt.plot(x, f(x), c='g', linewidth=5)
    pre_loss=loss
    loss=np.sum((y - (a * x + b)) ** 2)

    da=np.sum((a * x + b - y) * x)
    #亦可寫成如下
    #da= np.dot(a * x +b -y, x)

    db=np.sum(a * x + b - y)
    a = a - da * lr
    b = b - db * lr
    plt.plot(x, a*x+b, c='r')
    ax.text(-5, -4, f'損失值 {loss:.2f}', color='red')
    ax.text(-5, -5, f'迴歸函數 y={a:.7f}x+{b:.7f}', color='red')
    plt.pause(0.01)
plt.show()

B值

其實 b 值,是所有 y 值的平均數

import numpy as np
import pylab as plt
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False
n=20
np.random.seed(1)
x=np.linspace(-10,10, n)
y=0.5*x+3-np.random.randint(-5,5, n)
f=np.poly1d(np.polyfit(x, y, 1))
pre_loss=1_000_000_000
loss=100_000_000
lr=2.5e-3
a = 0
b = 0
fig=plt.figure(figsize=(9,6))
ax=fig.add_subplot()
b=y.mean()
while pre_loss-loss>0.001:
    ax.clear()
    ax.set_xlim(-12,12)
    ax.set_ylim(-6,10)
    ax.plot([0, 0], [-6, 10], color="blue", linewidth=0.5)
    ax.plot([-12, 12], [0, 0], color="blue", linewidth=0.5)

    plt.scatter(x, y, c='b')
    plt.plot(x, f(x), c='g', linewidth=5)
    pre_loss=loss
    loss=np.sum((y - (a * x + b)) ** 2)

    da=np.sum((a * x + b - y) * x)
    #亦可寫成如下
    #da= np.dot(a * x +b -y, x)

    db=np.sum(a * x + b - y)
    a = a - da * lr
    #b = b - db * lr
    plt.plot(x, a*x+b, c='r')
    ax.text(-5, -4, f'損失值 {loss:.2f}', color='red')
    ax.text(-5, -5, f'迴歸函數 y={a:.7f}x+{b:.7f}', color='red')
    plt.pause(0.01)
plt.show()

TensorFlow梯度下降

上述使用Python 迴圈跑了一萬次作梯次下降,每次迴圈都要使用.dot()計算紅色部份的內積(矩陣相乘)。但Python慢的要死,如果有上千萬個點要計算矩陣相乘呢,那不就會等到死。所以如果能用TensorFlow把矩陣相乘的計算推給GPU來計算呢!!

底下需指定優化器 optimizer,優化器的用途是指定階梯度下降的方法,常用的有SGD(stochastic gradient descent隨機梯度下降), Momentum(曼摩頓),Adam等等。

import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
import threading
import time
import pylab as plt
import numpy as np
import tensorflow as tf
import keras.optimizers.optimizer_experimental.optimizer def run(): a=tf.Variable(initial_value=0.) b=tf.Variable(initial_value=0.) variables=[a,b] epoch=1000 #優化器,每次逼近的方法,SGD為隨機逼近, 需給定逼近的學習率 #學習率太高,會造成無窮大值 #2.5e-4是測式出來的結果,因為要人工測試,所以是監督式學習 optimizer=keras.optimizers.SGD(learning_rate=2.5e-4) reg=np.poly1d(np.polyfit(x, y, 1)) for i in range(epoch): with tf.GradientTape() as f: y_pred=a * x + b#數學的函數原型 loss=tf.reduce_sum(tf.square(y_pred-y))#此法會啟動GPU #loss = tf.reduce_sum((y_pred - y)**2)#此法不會啟動GPU y_grad=f.gradient(loss, variables)#開始進行微分計算,將a, b 值放在 variables optimizer.apply_gradients(grads_and_vars=zip(y_grad, variables))#開始逼近,計算a,b值 ax.clear() ax.set_xlim(xl, xr) ax.set_ylim(yb, yt) ax.plot([0,0], [yb, yt], color="blue", linewidth=0.5) ax.plot([xl, xr], [0, 0], color="blue", linewidth=0.5) ax.scatter(x, y) #繪制numpy計算出來的迴歸線 ax.plot(x, reg(x), color='green', linewidth=5) #繪制 tensorflow 計算出來的逼近線 ax.plot(x, variables[0].numpy() * x + variables[1].numpy(), color='red') plt.draw() time.sleep(0.01) print(variables[0].numpy(), variables[1].numpy()) fig=plt.figure(figsize=(9,6)) rng=np.random.RandomState(1) xl=-15;xr=15;yb=-8;yt=12;n=20 x = np.linspace(-10, 10, n) y = 0.5 * x + 3 - rng.randint(-5, 5, n) ax=fig.add_subplot() t=threading.Thread(target=run) t.start() plt.show()

優化器條件

上述的優化器,使用 SGD,learning_rate為 2.5e-4

若將優化器改為 Adam, learning_rate 改為 5e-2, 也是同樣的效果

損失函數定義為 “預測值跟實際值的差異”,稱為 Loss。這個定義,也就是迴歸線的定義。

迴歸線定義為
(預測的 y 值 – 實際的 y 值)平方總合,再求平均數,以公式表示為

$(Loss=\frac{1}{n}\sum_{i=1}^{n}(y_{i}-\tilde{y_{i}})^{2})$

二階迴歸線偏微分

二階迴歸線的公式為 $(y=ax^2+bx+c)$,所以

$(Loss(a,b))$
$(=\frac{1}{n}\sum_{i=1}^{n}(y_{i}-\tilde{y_{i}})^{2})$
$(=\frac{1}{n}\sum_{i=1}^{n}(y_{i}-(ax_i^2+bx_i+c))^{2})$
$(=\frac{1}{n}\sum_{i=1}^{n}y_{i}^2-2y_i(ax_i^2+bx_i+c)+(ax_i^2+bx_i+c)^2)$
$(=\frac{1}{n}\sum_{i=1}^{n}y_{i}^2-2ay_ix_i^2-2by_ix_i-2cy_i+a^2x_i^4+2ax_i^2(bx_i+c)+(bx_i+c)^2)$
$(=\frac{1}{n}\sum_{i=1}^{n}y_{i}^2-2ay_ix_i^2-2by_ix_i-2cy_i+a^2x_i^4+2abx_i^3+2acx_i^2+b^2x_i^2+2bcx_i+c^2)$

對 a 偏微分
$(\frac{\partial Loss(a,b,c)}{\partial a})$
$(=2\sum_{i=1}^{n}-y_ix_i^2+ax_i^4+bx_i^3+cx_i^2)$
$(=2\sum_{i=1}^{n}(ax_i^2+bx_i+c-y_i)x_i^2)$
$(=2\sum_{i=1}^{n}(\tilde{y_i}-y_i)x_i^2)$

對 b 偏微分
$(\frac{\partial Loss(a,b,c)}{\partial b})$
$(=2\sum_{i=1}^{n}-y_ix_i+ax^3+bx_i^2+cx_i)$
$(=2\sum_{i=1}^{n}(ax_i^2+bx_i+c-y_i)x_i)$
$(=2\sum_{i=1}^{n}(\tilde{y_i}-y_i)x_i)$

對 c 偏微分
$(\frac{\partial Loss(a, b, c)}{\partial c})$
$(=2\sum_{i=1}^{n}-y_i+ax_i^2+bx_i+c)$
$(=2\sum_{i=1}^{n}\tilde{y_i}-y_i)$

n 階迴歸線偏微分總結

由上可知,如果是 n 階迴歸線的話
對 a 偏微分就是 $(=2\sum_{i=1}^{n}(\tilde{y_i}-y_i)x_i^n)$
對 b 偏微分就是 $(=2\sum_{i=1}^{n}(\tilde{y_i}-y_i)x_i^{n-1})$
對 c 偏微分就是 $(=2\sum_{i=1}^{n}(\tilde{y_i}-y_i)x_i^{n-2})$
到最後是
$(2\sum_{i=1}^{n}(\tilde{y_i}-y_i)x_i^1)$
$(2\sum_{i=1}^{n}(\tilde{y_i}-y_i)x_i^0)$

發佈留言

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