損失函數
損失函數就是在定義預測值跟實際值的差異,稱為Loss。損失函數怎麼定義依個人喜好,反正能說出一套讓眾人信服的理論,就是好的演算法。
迴歸線是一種定義的方法,PCA又是另一種定義的方法。
迴歸線損失函數定義為
(預測的 y 值 – 實際的 y 值)平方總合,再求平均數,以公式表示為
$(Loss=\frac{1}{n}\sum_{i=1}^{n}(\tilde{y_{i}}-y_{i})^{2})$
$(y_{i})$ 是經由假設的 y = ax + b 所計算出來的值,$(\tilde{y_{i}})$ 則是實際的 y 值。
然後把上面的公式代入
$(Loss(a,b)=\sum_{i=1}^{n}(\tilde{y}_{i}-(ax_{i}+b))^2=\sum_{i=1}^{n}(\tilde{y_{i}}^2-2\tilde{y}_{i}ax_{i}-2\tilde{y}_{i}b+a^2x_{i}^2+2ax_{i}b+b^2))$
對 a 偏微分
$(\frac{\partial Loss(a,b)}{\partial a}=\sum_{i=1}^{n}(-2\tilde{y}_{i}x_{i}+2ax_{i}^2+2x_{i}b)=2\sum_{i=1}^{n}(-\tilde{y}_{i}+ax_{i}+b)x_{i}=2\sum_{i=1}^{n}(y_{i}-\tilde{y}_{i})x_{i})$
對 b 偏微分
$(\frac{\partial Loss(a,b)}{\partial b}=\sum_{i=1}^{n}(-2\tilde{y}_{i}+2ax_{i}+2b)=2\sum_{i=1}^{n}(ax_{i}+b-\tilde{y}_{i})=2\sum_{i=1}^{n}(y_{i}-\tilde{y}_{i}))$
從上面的例子,就可以清楚的明白,不論是微分還是偏微分,其對象是損失函數。即然是損失函數,就是實際值與預估值的差異,當然是愈小愈好,這就是為什麼要找極值的原因了。
批次梯度下降法(Batch Gradient Descent : BGD)
BGD 是最基本的梯度下降法,一般是使用 $(x = x +\bigtriangledown f(x))$ 來逼近,但 $(\bigtriangledown f(x))$ 的值通常都非常的大,所以會再乘上一個極小的值 lr ,這個 lr 的值通常會小於 1 ,甚至到非常的小。所以公式會變成 $(x = x + lr * \bigtriangledown f(x))$,我們把 lr 這個值稱為學習率。
學習率就是 x 軸的步進值縮小化,學習率 * 斜率 = 下一次 x 的步進值。如下的代碼用圖解的方式來表達。
import threading
import time
import numpy as np
import matplotlib.pyplot as plt
#目標函數 y=x^2
def f(x):
return np.square(x)
#目標函數的一階導數 dy/dx=2*x
def df(x):
return 2 * x
def bias(a,x):
b=f(x) - a * x
return b
def runnable():
xs = np.linspace(-10, 10, 100)
x=-10
ys=f(xs)
point_x=[x]
for i in range(epochs):
ax.clear()
ax.set_xlim(-10,10)
ax.set_ylim(-50, 200)
plt.plot(xs, ys)
plt.scatter(point_x, f(point_x), c='r')
#對目標函數進行微分
a=df(x)
b=bias(a, x)
#畫導線
x_l=x-3
x_r=x+3
line_x=[x_l, x_r]
line_y=[a*(x_l)+b,a*(x_r)+b]
plt.plot(line_x, line_y)
ax.text(-5,-15, f'{a} * x + {b}', color='red')
plt.draw()
#計算下一步 (x,y)
dx=df(x)
x += -dx *lr
point_x.append(x)
time.sleep(0.1)
plt.figure(figsize=(10,6))
epochs = 200
lr = 0.2
ax=plt.subplot()
t=threading.Thread(target=runnable)
t.start()
plt.show()
這裏有一個很怪異的問題,為什麼負斜率逐漸變成水平後,不會再度變成正斜率再度往上爬升呢? 這是因為 $(\bigtriangledown f(x) \rightarrow 0)$ ,然後再乘上極小的 lr ,就更加接近 0 。
所以 $(x = x + lr * \bigtriangledown f(x) \doteq x + 0 \doteq x)$,所以 x 就會停留在原本的位置,不會繼續往右邊走。
Tensorflosw 版
底下是使用 Tensorflow 的版本,不過比上面的慢很多
import threading import time import numpy as np import matplotlib.pyplot as plt import tensorflow as tf #目標函數 y=x^2 def f(x): return np.square(x) #計算偏移量 b 值 def bias(a,x): b=f(x)- a * x return b def runnable(): xs = tf.linspace(-10, 10, 100) ys=f(xs) x=tf.Variable(-10.)#x不可以使用 tf.constant, 一定要用 Variable point_x=[x] for i in range(epochs): with tf.GradientTape() as tape: y = tf.pow(x, 2) ax.clear() ax.set_xlim(-10,10) ax.set_ylim(-50, 200) plt.plot(xs, ys) plt.scatter(point_x, f(point_x), c='r') #進行微分 a=tape.gradient(y,x) b=bias(a, x) #繪製導線 x_l=x-3 x_r=x+3 line_x=[x_l, x_r] line_y=[a*(x_l)+b,a*(x_r)+b] plt.plot(line_x, line_y) ax.text(-5, -15, f'{a} * x + {b}', color='red') plt.draw() #計算下一步 (x, y) v= -a * lr x=tf.Variable(x+v) print("x =",x) point_x.append(x) time.sleep(0.01) plt.figure(figsize=(10,6)) epochs = 200 lr = 0.2 ax=plt.subplot() t=threading.Thread(target=runnable) t.start() plt.show()
放大學習率
將上述的程式碼中,把 lr 更改為 0.95 ,就會看到導線左右二邊跳動。
..........
xs = np.linspace(-5, 5, 100)
ys = f(xs)
epochs = 200
lr = 0.95
ax=plt.subplot()
t=threading.Thread(target=runnable)
t.start()
plt.show()
過大的學習率
接下來把學習率 lr 改成 1.0,會發現導線在左右二邊持續跳動,根本就無法收斂。
鞍點
當函數為多次方程式時,就會有鞍點的產生,如下是四次方程式$(f(x)=x^{4}-60x^{3}-x+1)$ 的圖形
import numpy as np import pylab as plt shrink_y=1e6 ax=plt.subplot() x=np.linspace(-30,60,100) y=(np.power(x,4)-60*pow(x,3)-x+1)/shrink_y ax.set_xlim(-45, 70) ax.set_ylim(-2, 3) plt.plot(x, y) plt.savefig("saddle_1.jpg") plt.show()