---
# System prepended metadata

title: PyTorch學習記錄_2_非線性模型
tags: [PyTorch, Python, DeepLearning]

---

# PyTorch學習記錄_2_非線性模型
線性模型本身是一種非常簡單的結構，不過現實中的資料多半是非線性模型才比較能夠有效的預測。非線性模型在架構上與線性模型之間就只是一個非常簡單的差異。

## 非線性模型
首先引入需求的模型：
```python
import torch
import torch.nn as nn
import torch.optim as optim
```

定義虛擬的資料集：
```python
X = torch.tensor([
    [1.0], [1.5], [2.0], [2.5], [3.0], [3.5], [4.0], [4.5], [5.0], [5.5],
    [6.0], [6.5], [7.0], [7.5], [8.0], [8.5], [9.0], [9.5], [10.0], [10.5],
    [11.0], [11.5], [12.0], [12.5], [13.0], [13.5], [14.0], [14.5], [15.0], [15.5],
    [16.0], [16.5], [17.0], [17.5], [18.0], [18.5], [19.0], [19.5], [20.0]
], dtype=torch.float32)

Y = torch.tensor([
    [6.97], [9.66], [12.12], [14.55], [16.76], [21.71], [26.53], [32.46], [37.14], [42.36],
    [46.11], [52.97], [57.75], [61.27], [66.16], [67.62], [69.44], [71.56], [72.81], [73.87],
    [76.33], [76.37], [78.35], [80.06], [81.85], [84.46], [83.99], [86.54], [88.32], [86.84],
    [89.25], [88.12], [88.15], [91.78], [92.26], [92.14], [90.74], [90.38], [92.97]
], dtype=torch.float32)
```

列印資料點：
![image](https://hackmd.io/_uploads/B1QRSDDXZx.png)

從資料分佈來看，很明顯的這如果單純的使用線性模型是無法有效的擬合，所以我們需要的是非線性模型。

一般來說，我們在訓練模型之前都會將資料正規化，這對於模型的收斂有著極大的效益，一般結構式資料也許就是縮放為均值為0，變異數為1的資料，或是影像資料普遍除`255.0`都是一種資料縮放的正規化處理。

```python
X_mean = X.mean()
X_std = X.std()

Y_mean = Y.mean()
Y_std = Y.std()

X_norm = (X - X_mean) / X_std
Y_norm = (Y - Y_mean) / Y_std
```

請注意，如果我們有針對目標值來做縮放的話，那預測的結果就必需做相對應的『Inverse(反向)』處理，近期也確實有一些論文指出對目標值(`Y`)做正規化對於數值型的預測是有幫助的。

縮放之後的值域如下：
![image](https://hackmd.io/_uploads/rk7fpSk4Ze.png)

現在我們可以建構『非』線性模型：
```python
torch.manual_seed(25)

model = nn.Sequential(
    nn.Linear(1, 3),
    nn.ReLU(),
    nn.Linear(3, 1)
)
```

可以注意到這跟線性模型最大的差異在於加入`ReLU`這個`activation funciton`，那這個`activation funciton`有很多中文，活化函數、激活函數、啟動函數，都好，總之就是一個非線性轉換的函數，只是不同的函數有著不一樣的行為就是。

然後設置損失函數與最佳化的方式：
```python
loss_function = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
```

最後就是Do、Re、Mi、So：
```python
for epoch in range(3000):
    # 每一次迭代開始訓練之前記得清空梯度資訊
    optimizer.zero_grad()
    # 取得模型預測結果
    outputs = model(X_norm)
    # 利用損失函數計算實際資料與模型輸出之間的差異
    loss = loss_function(outputs, Y_norm)
    # 計算反向傳播的資訊 
    loss.backward()
    # 更新學習參數
    optimizer.step()
        
print("\nTraining Complete.")
print(f"\nFinal Loss: {loss.item()}")
# Final Loss: 0.003752670716494322
```

最終的學習結果如下：
![image](https://hackmd.io/_uploads/SkwTASyVZe.png)

上圖來看不難發現到，模型很好的捕捉到資料的趨勢，並且最後的`loss`也非常完美的收斂。

眼尖的你也許有注意到，上圖的單位並不正確。是的，所以標準化後的資料就必需要再反向處理還原：
```python
# 讓torch知道這次的推論不用計算梯度
with torch.no_grad():
    predicted_norm = model(X_norm)

# 正規化是`(資料-均值)/標準差`，反向處理就是`(資料 * 標準差) + 均值`
predicted_Y = (predicted_norm * Y_std) + Y_mean
```

單位還原之後的擬合結果如下圖：
![image](https://hackmd.io/_uploads/SJEmeUkEWe.png)

## 結論
簡單的非線性模型跟線性模型之間差了一個`activation function`，這是一個值得研究的議題，基本上常用的還是那幾個萬年不死的函數，經過時間驗證過的還是最靠譜。

另外，目標值的縮放並不一定要處理，只是研究顯示當值域落差很大的情況下，做一下對於模型的訓練也是有好處的。記得，分類任務絕對不要做。