此篇難度頗高,有許多數學公式及微積分。本篇是進入 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 * lrplt.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)$
