損失函數

      在〈損失函數〉中留言功能已關閉

損失函數

損失函數就是在定義預測值跟實際值的差異,稱為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()