# <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()
```