# <center>Day 5 Note</center> ###### tags:`Tutorial` [TOC] (本文使用Py及其下套件實作) (以讀者具有Py語法先備知識為前提寫作) 還記得筆者國中做科展的時候 都是用Excel裡面的圖表功能 把數據整理出來推測出合理的函數 當時都不知道是如何做到的 這次就要來實作 ## 線性回歸 說到Excel圖表裡的回歸功能 在數學上叫做線性回歸 當我們給定大量測資時 我們不能保證所有測資可以形成一個漂亮的函數圖形 因此我們就會需要這個功能來做輔助 實際上就是給定大量數據讓AI去推演出較為合理的函數圖形 要怎麼做呢 讓我們往下看 ### 預測模型 先看圖 <img src="https://i.imgur.com/4ElXsLu.png=50x50" width="50%" height="50%" /> 圖中的小點是我們給予電腦的資料 也就是有著一堆<font size=4>( x<sub>i</sub> , y<sub>i</sub> )</font>的群組 而斜直線則是AI預測的函數 <font size=4>(L : Y=β<sub>0</sub>+β<sub>1</sub>x)</font> 可想而知 我們要獲得最符合資料統整結果的直線L 就需要讓每個測資對應函數的距離達到最小 亦即<font size=4>ε<sub>i</sub>=y<sub>i</sub>-Y<sub>i</sub></font>須達到最小化 ### 找最小值 那我們要如何才能找到最適合的回歸函數呢 換句話說 我們要找到最適合的β<sub>0</sub>和β<sub>1</sub> 其實不難 只要把對於所有測資的<font size=4>ε<sub>i</sub></font>找出來相加再找出最小值就好 首先要處理的第一件事 <font size=5>要先處理掉ε<sub>i</sub>的正負差</font> 這裡要注意一點<font size=4>ε<sub>i</sub></font>在算的時候只要<font size=4>y<sub>i</sub><Y<sub>i</sub></font> <font size=4>ε<sub>i</sub></font>就會是負的 為了避免掉正負相加會抵消 這裡要搬出一個原則叫==最小平方法== 先平方再加能有效避免掉正負相間的問題 好的 這時候我們可以得出一個式子 $$L( β_0 , β_1 )=\frac {1} {2}Σ[y_i-(β_0+β_1x)]^2$$ 接下來為了讓目標函數具有最小值: 目標函數分別對β0、β1取偏微分的值必須為零 * $$\frac {∂L( β_0 , β_1 )} {∂β_0}=Σ[y_i-(β_0+β_1x)]=0$$ * $$\frac {∂L( β_0 , β_1 )} {∂β_1}=Σ[y_i-(β_0+β_1x)]x_i=0$$ 大概長這樣(偷偷幫你省略掉中間一大串化簡步驟) ### 梯度下降 這時候你可能會想 欸?難不成我們直接用公式的方式得到最小值嗎 但我們要如何找到那個所謂的最小值呢 這時候要搬出一個方法 叫==梯度下降== 地球人都知道 求導函數即為求該點的斜率 而當導函數值為0時會有極值 此時我們可以有個大膽的想法 先看看圖 <img src="https://i.imgur.com/IgiXOZs.png=0x30" width="200" height="270" /> 發現了嗎 你得到的斜率會告訴你該往哪走才會到極值(也就是所謂的谷底) * 若在函數圖形上切線斜率大於0的位置 則極值在X向左移的方向 * 若在函數圖形上切線斜率小於的位置 則極值在X向右移的方向 我們可以讓x慢慢移動使其接近谷底 修正的方法為 $$x=x-η\frac{dy}{dx}$$ η我們稱之為學習率 意即每次修正的幅度 這裡要注意的是 * 學習率η若太大可能發生無法收斂 * 學習率η若太小可能發生收斂速度太慢 因此我們要讓AI去學習 逐步去修正β<sub>0</sub>跟β<sub>1</sub> 可以列出式子 * $$β_0:β_0-η\frac {∂L( β_0 , β_1 )} {∂β_0}=β_0-ηΣ[y_i-(β_0+β_1x)]$$ * $$β_1:β_1-η\frac {∂L( β_0 , β_1 )} {∂β_1}=β_1-ηΣ[y_i-(β_0+β_1x)]x_i$$ 此時大致上理論就成形了 讓我們來試做看看 ### 實作 理論的部分先緩一下 讓我們把上面那坨東東先打成程式 首先先define出我們的目標函數 好讓我們之後驗證可以用 ```python= def f(x): return b0 + b1 * x ``` 當然 b0跟b1不能白有 現在沒有真的學習資料 這裡我們選擇讓它們隨機開始去假裝有這些資料 (由於後面還會用到 於是這裡是直接使用numpy裡面的rand) ```python= import numpy as np b0 = np.random.rand() b1 = np.random.rand() ``` 再來就是要給予我們的資料值 這裡使用矩陣來存 方便我們運算可以一口氣運算 ```python= data_x=np.array([裡面是一坨資料]) data_y=np.array([裡面是一坨資料]) ``` 接著我們分割資料 讓一部份的資料可以拿來學習 另一部分的資料可以拿來驗算 當然你也可以選擇全部拿來學習 再另行人工驗證也可以 這裡採用的是前者 分成兩半進行 ```python= t_x=data_x[:len(data_x)//2] t_y=data_y[:len(data_x)//2] v_x=data_x[len(data_x)//2:] v_y=data_y[len(data_x)//2:] ``` 接著就是訓練了 我們要讓機器透過學習來修正自己的參數 我們姑且讓機器學習個20000次 並將學習率設定為0.0001 ```python= ETA=0.0001 t_ct=20000 def train(): global b0 global b1 for i in range(t_ct): b0 -= ETA * np.sum((f(t_x) - t_y)) b1 -= ETA * np.sum((f(t_x) - t_y) * t_x) ``` Do Re Mi So~ 這樣就AI就學習了20000次了 理論上β<sub>0</sub>跟β<sub>1</sub>就已經被修正到接近目標函數了 接下來就讓我們看看結果吧 我們使用matplotlib的圖表功能來顯示我們的結果 ```python= import matplotlib.pyplot as plt def check(): x = np.linspace(0,30, 2000) plt.figure(figsize=(8,6)) plt.title("train-data",fontsize=20,color='white') plt.xticks(range(0,35,5),color='white') plt.yticks(range(300,700,100),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() ``` 結果長這樣 漂亮吧 <img src="https://i.imgur.com/WMOLozC.png" width="" height="250" /> ## 優化 這時候出現了個問題 當我們某個特徵範圍過大 而另一個太小 當我們在做梯度下降時 收斂的複雜度會比特徵平均的資料還要來的複雜許多 這時我們可以透過一些簡單的優化來提升效率 ### 特徵縮放 如上所述 特徵縮放是將特徵資料按比例縮放 讓資料落在某一特定的區間 去除數據的單位限制 將其轉化為純數值 以便不同單位的特徵能夠進行比較 除了可以優化梯度下降法 還能提高精確度 講起來很複雜 大致上就是高一統計學的兩種方法 1. 標準化 將所有特徵數據縮放成平均為0、標準差為單位 2. 歸一化 將特徵數據按比例縮放到0到1的區間 做起來一點也不難 ### 標準化 $$x'=\frac{data-min(data)}{fullrange(data)}$$ ### 歸一化 $$x'=\frac{data-arg(data)}{std(data)}$$ 簡單吧 做成程式也不難 只要把data_x修改一下就好 在程式中加入 ```python= 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) data_x=fc(data_x) ``` 為了能方便選擇要標準化還是歸一化 筆者還特地設計了一個value t可以讓我們選擇 <img src="https://i.imgur.com/ABEROLu.png" width="" height="250" /> 畫出來的圖形跟原本的差不多 只是由於特徵縮放後data_x的範圍變了 線段的繪製範圍要稍作修改圖表才會比較好看 ## 心得 最基礎的線性規劃大致上就是這樣 課程中其實還有教你如何顯示出β<sub>0</sub>和β<sub>1</sub>的修正過程 還有繪製動畫 甚至是推廣至高次多項式的回歸 但礙於版面因素筆者就不多作補充 身為小高二的筆者要應付一卡車的高三數學著實花了不少腦筋了 好在有先偷學一點微積分跟線性規劃(汗 不然可能真的會應付不來呢 ## 展望 未來能夠將其處理為物件模型 再用db去儲存這些資料模型 未來就有現成的學習資料可以用了 ## Code 以下是筆者自己實作時的code 由於功能有些許差異 可能與上面敘述有些出入就是了 (資料係課堂統一給定數據) ```python= 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() ```