Day 5 Note
(本文使用Py及其下套件實作)
(以讀者具有Py語法先備知識為前提寫作)
還記得筆者國中做科展的時候
都是用Excel裡面的圖表功能
把數據整理出來推測出合理的函數
當時都不知道是如何做到的
這次就要來實作
線性回歸
說到Excel圖表裡的回歸功能
在數學上叫做線性回歸
當我們給定大量測資時
我們不能保證所有測資可以形成一個漂亮的函數圖形
因此我們就會需要這個功能來做輔助
實際上就是給定大量數據讓AI去推演出較為合理的函數圖形
要怎麼做呢 讓我們往下看
預測模型
先看圖
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
圖中的小點是我們給予電腦的資料
也就是有著一堆( xi , yi )的群組
而斜直線則是AI預測的函數 (L : Y=β0+β1x)
可想而知
我們要獲得最符合資料統整結果的直線L
就需要讓每個測資對應函數的距離達到最小
亦即εi=yi-Yi須達到最小化
找最小值
那我們要如何才能找到最適合的回歸函數呢
換句話說 我們要找到最適合的β0和β1
其實不難
只要把對於所有測資的εi找出來相加再找出最小值就好
首先要處理的第一件事
要先處理掉εi的正負差
這裡要注意一點εi在算的時候只要yi<Yi
εi就會是負的
為了避免掉正負相加會抵消
這裡要搬出一個原則叫最小平方法
先平方再加能有效避免掉正負相間的問題
好的 這時候我們可以得出一個式子
接下來為了讓目標函數具有最小值:
目標函數分別對β0、β1取偏微分的值必須為零
大概長這樣(偷偷幫你省略掉中間一大串化簡步驟)
梯度下降
這時候你可能會想
欸?難不成我們直接用公式的方式得到最小值嗎
但我們要如何找到那個所謂的最小值呢
這時候要搬出一個方法 叫梯度下降
地球人都知道
求導函數即為求該點的斜率
而當導函數值為0時會有極值
此時我們可以有個大膽的想法
先看看圖
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
發現了嗎 你得到的斜率會告訴你該往哪走才會到極值(也就是所謂的谷底)
- 若在函數圖形上切線斜率大於0的位置
則極值在X向左移的方向
- 若在函數圖形上切線斜率小於的位置
則極值在X向右移的方向
我們可以讓x慢慢移動使其接近谷底
修正的方法為
η我們稱之為學習率 意即每次修正的幅度
這裡要注意的是
- 學習率η若太大可能發生無法收斂
- 學習率η若太小可能發生收斂速度太慢
因此我們要讓AI去學習 逐步去修正β0跟β1
可以列出式子
此時大致上理論就成形了
讓我們來試做看看
實作
理論的部分先緩一下
讓我們把上面那坨東東先打成程式
首先先define出我們的目標函數 好讓我們之後驗證可以用
當然 b0跟b1不能白有
現在沒有真的學習資料
這裡我們選擇讓它們隨機開始去假裝有這些資料
(由於後面還會用到 於是這裡是直接使用numpy裡面的rand)
再來就是要給予我們的資料值
這裡使用矩陣來存 方便我們運算可以一口氣運算
接著我們分割資料
讓一部份的資料可以拿來學習
另一部分的資料可以拿來驗算
當然你也可以選擇全部拿來學習 再另行人工驗證也可以
這裡採用的是前者 分成兩半進行
接著就是訓練了
我們要讓機器透過學習來修正自己的參數
我們姑且讓機器學習個20000次
並將學習率設定為0.0001
Do Re Mi So~
這樣就AI就學習了20000次了
理論上β0跟β1就已經被修正到接近目標函數了
接下來就讓我們看看結果吧
我們使用matplotlib的圖表功能來顯示我們的結果
結果長這樣
漂亮吧
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
優化
這時候出現了個問題
當我們某個特徵範圍過大 而另一個太小
當我們在做梯度下降時
收斂的複雜度會比特徵平均的資料還要來的複雜許多
這時我們可以透過一些簡單的優化來提升效率
特徵縮放
如上所述
特徵縮放是將特徵資料按比例縮放
讓資料落在某一特定的區間
去除數據的單位限制
將其轉化為純數值
以便不同單位的特徵能夠進行比較
除了可以優化梯度下降法
還能提高精確度
講起來很複雜 大致上就是高一統計學的兩種方法
- 標準化
將所有特徵數據縮放成平均為0、標準差為單位
- 歸一化
將特徵數據按比例縮放到0到1的區間
做起來一點也不難
標準化
歸一化
簡單吧 做成程式也不難
只要把data_x修改一下就好
在程式中加入
為了能方便選擇要標準化還是歸一化
筆者還特地設計了一個value t可以讓我們選擇
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
畫出來的圖形跟原本的差不多
只是由於特徵縮放後data_x的範圍變了
線段的繪製範圍要稍作修改圖表才會比較好看
心得
最基礎的線性規劃大致上就是這樣
課程中其實還有教你如何顯示出β0和β1的修正過程
還有繪製動畫
甚至是推廣至高次多項式的回歸
但礙於版面因素筆者就不多作補充
身為小高二的筆者要應付一卡車的高三數學著實花了不少腦筋了
好在有先偷學一點微積分跟線性規劃(汗
不然可能真的會應付不來呢
展望
未來能夠將其處理為物件模型
再用db去儲存這些資料模型
未來就有現成的學習資料可以用了
Code
以下是筆者自己實作時的code
由於功能有些許差異
可能與上面敘述有些出入就是了
(資料係課堂統一給定數據)
import numpy as np
import matplotlib.pyplot as plt
from IPython import display
data_x = np.array([24, 22, 15, 4, 9, 20, 5, 3, 17, 19, 13, 10, 12, 11, 16, 27, 16, 16, 6, 20])
data_y = np.array([591, 543, 410, 310, 319, 520, 338, 330, 501, 508, 399, 331, 390, 390, 431, 660, 409, 430, 323, 524])
def fc(x,t):
return (x-np.min(x))/(np.max(x)-np.min(x)) if t==1 else (x-np.mean(x))/np.std(x)
def f(x):
return b0 + b1 * x
def Loss(x, y):
return 0.5 * np.sum((y - f(x)) ** 2)
t_x=fc(data_x[:len(data_x)//2],1)
t_y=data_y[:len(data_y)//2]
v_x=fc(data_x[len(data_x)//2:],1)
v_y=data_y[len(data_y)//2:]
b0=np.random.rand()
b1=np.random.rand()
ETA = 0.001
t_ct = 20000
LL=[]
Step0=[]
Step1=[]
def train():
global ct
global b0
global b1
for i in range(t_ct):
b0 = b0 - ETA * np.sum((f(t_x) - t_y))
b1 = b1 - ETA * np.sum((f(t_x) - t_y) * t_x)
current_loss = Loss(t_x, t_y)
ct += 1
if not ct%500:
LL.append(current_loss)
Step0.append(b0)
Step1.append(b1)
def check():
x = np.linspace(0,1,2000)
plt.figure(figsize=(8,6))
plt.title("train-data",fontsize=20,color='white')
plt.xticks(range(0,2),color='white')
plt.yticks(range(0,2),color='white')
plt.xlabel("x",fontsize=20,color='white')
plt.ylabel("y",fontsize=20,color='white')
plt.plot(x, f(x),label="mod func")
plt.plot(t_x, t_y, 'go',label="train data")
plt.plot(v_x, v_y, 'ro',label="valid data")
plt.legend()
plt.show()
def loss():
plt.figure(figsize=(8,5))
plt.title("change",fontsize=20,color='white')
plt.xlabel("train times(x100)",fontsize=20,color='white')
plt.ylabel("Loss",fontsize=20,color='white')
plt.plot(LL,":o")
plt.show()
def path():
for i in range(len(Step0)):
plt.figure(figsize=(8,5))
plt.title("Movement path",fontsize=20,color='white')
plt.xticks(range(0,2),color='white')
plt.yticks(range(0,2),color='white')
plt.xlabel("b0",fontsize=20,color='white')
plt.ylabel("b1",fontsize=20,color='white')
plt.scatter(Step0[:i], Step1[:i], s=20,color='r')
plt.pause(0.1)
display.clear_output(wait=True)
if __name__=="__main__":
train()
loss()
path()
check()