---
# System prepended metadata

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

---

# PyTorch學習記錄_1_線性模型
因為個人已經有 `tf` 、 `keras` 的經驗，因此這邊的記錄不會太過於詳細，內容主要來自於 Coursera 的系列課程 [PyTorch for Deep Learning Professional Certificate](https://www.coursera.org/professional-certificates/pytorch-for-deep-learning#courses)，還不錯，淺顯易懂，邊學還可以邊練習英聽，搭配沉侵式翻譯這個擴充的話可以即時翻譯成中文。

## 最簡單的範例
這是一個線性迴歸的範例，一條線，然後我們可以把資料點畫出來，再利用訓練後的模型來推論沒有看過的資料。

首先引入`torch`，模型相關的就會在`nn`，最佳化相關的的就是在`optim`，約定俗定上就是會用`nn`、`optim`來做為其使用的別名：
```python
import torch
import torch.nn as nn
import torch.optim as optim
```

設置資料點，也就是我們的特徵資料，四筆資料，一個特徵維度，型別為`float32`：
```python
dataset = torch.tensor([[1.0], [2.0], [3.0], [4.0]], dtype=torch.float32)
```

設置目標值，也就是相對應特徵所映射的目標值，四筆資料就對應四個目標值，型別為`float32`：
```python
target_value = torch.tensor([[6.95], [12.12], [16.78], [22.20]], dtype=torch.float32)
```

設置訓練模型，因為是個簡單的範例，因此直接以`Sequential`配置，這跟`tf`中的`Sequential`是一樣的概念，就是由上而下按序執行，早期的模型確實用這種方式建模就可以滿足，但是現代化的模型已經是不可能這樣處理的，但是對於過程中的某一個`block`來說，這樣的配置還是需要存在的：
```python
model = nn.Sequential(nn.Linear(1, 1))
```
- 這邊的配置說明的是 nn.Linear 接受一個input，產出一個output
- input與output的維度則是取決於特徵維度與目標結構，以我們的範例來說，特徵是一個維度，輸出也是一個浮點數，因此設置為`(1, 1)`

設置損失函數與最佳化方式，損失函數`loss function`指的是評估模型的預測值與實際目標值之間的差異有多少，而最佳化方式指的是要採用那一種方式來收斂預測值與目標值之間的差異：
```python
loss_function = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
```
- 這邊的損失函數採用的是均方誤差，還有很多方法，甚至可以自定義，這是依實際狀況來決定的
- 這邊的最佳化方式採用的是`SGD`，隨機梯度下降，另外還有`adam`、`rmsprop`等
- `SGD`內的`model.parameters()`指的是模型內的所有學習參數，也就是我們在說的權重
- `SGD`內的`lr`指的是學習效率，這是超參數的一種，最好的方式是利用尋參來決定最佳值，有興趣可再深入研究，沒興趣就用男人的第六感來決勝負即可

前置完成之後我們就可以進入訓練階段：
```python
for epoch in range(500):    
    optimizer.zero_grad()    
    outputs = model(dataset)
    loss = loss_function(outputs, target_value)    
    loss.backward()    
    optimizer.step()
    if (epoch + 1) % 50 == 0:
        print(f"Epoch {epoch + 1}: Loss = {loss.item()}")
```
- `epoch`，定義整個資料集要訓練幾個迭代
- `optimizer.zero_grad()`，讓梯度歸零，這很重要，如果沒有這麼做的話會造成梯度累積
- `outputs = model(distances)`，把資料集丟進去模型得到一個預測值，注意到，這邊這樣的作法是把所有的資料集一口氣的丟進去，以我們的資料集為例，有四筆資料，因此丟進去之後會得到四個輸出
- `loss = loss_function(outputs, times)`，計算預測值與實際值之差的差異有多少
- `loss.backward()`，利用損失函數得到的差異值來執行反向傳播
- `optimizer.step()`，更新模型參數

這只是我的想法，我覺得這邊蠻不直觀的，如果是`optimier.step(loss)`好像比較直觀，甚至是`loss.backward()`的計算是不是應該要`loss.backward(model.parameters)`，這樣比較能說明這個`loss`的反向傳播是作用在那，不過這只是我的個人看法就是。

執行訓練之後我們會看到下面的結果：
```
Epoch 50: Loss = 0.08988543599843979
Epoch 100: Loss = 0.07318146526813507
Epoch 150: Loss = 0.06080767512321472
Epoch 200: Loss = 0.05163940414786339
Epoch 250: Loss = 0.044846080243587494
Epoch 300: Loss = 0.03981277346611023
Epoch 350: Loss = 0.03608356788754463
Epoch 400: Loss = 0.03332036733627319
Epoch 450: Loss = 0.03127291053533554
Epoch 500: Loss = 0.02975599467754364
```
- 這邊呈現的就是訓練過程中損失函數給出的差異的收斂過程

下面圖表說明學習的成果：
![image](https://hackmd.io/_uploads/ByuNxmfQbx.png)
- `x`軸為特徵值
- `y`軸為結果，黃點為實際資料點，綠線為預測趨勢線
- 結果來看，預測趨勢線非常貼近實際資料點，這是好事，代表預測值接近實際值

## 使用訓練好的模型

現在我們要來瞭解怎麼使用訓練好的模型，訓練模型跟使用模型在執行流程上有很多差異，不過這邊先不多談，最直接的一點就是，訓練模型過程中我們會利用梯度來更新權重，但是使用模型的過程中我們並不需要考慮這些，因此：
```python
with torch.no_grad():
    new_datapoint = torch.tensor([[10.0]], dtype=torch.float32)
    predicted = model(new_datapoint)
    print(f"Prediction for new datapoint 10: {predicted.item():.1f}")
```
- `with torch.no_grid()`，這邊的定義是在說著這不是訓練，不用計算梯度了，這可以加速推論速度
- `model(new_datapoint)`，把要預測的資料點做為輸入提供給予模型

執行結果如下：
```
Prediction for new datapoint 10: 52.1
```

## 模型的內部資訊
這個範例中的模型是一個很簡單的結構，就只有中間一個線性層，然後這個線性層就只有一個權重，我們可以嚐試看一下這個線性層所提供的資訊：
```python=
layer = model[0]
weights = layer.weight.data.numpy()
bias = layer.bias.data.numpy()

print(f"Weight: {weights}")
print(f"Bias: {bias}")
```
- `layer = model[0]`，取得模型第一層的結構資料
- `layer.weight.data.numpy()`，取得第一層的權重資料並轉為`numpy`
- `layer.bias.data.numpy()`，取得第一層的偏差資料並轉為`numpy`

執行之後的結果如下：
```
Weight: [[5.015503]]
Bias: [1.9849645]
```

## 結論
放棄`tensorflow`展開新的`pytorch`人生