# 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`,這是一個值得研究的議題,基本上常用的還是那幾個萬年不死的函數,經過時間驗證過的還是最靠譜。 另外,目標值的縮放並不一定要處理,只是研究顯示當值域落差很大的情況下,做一下對於模型的訓練也是有好處的。記得,分類任務絕對不要做。