---
# System prepended metadata

title: PyTorch_NN_HW1

---

## W1

### 設定環境
1-1. 使用python 3.10的虛擬環境
```
py -3.10 -m venv env
```
1-2. 進入虛擬環境
```
.\env\Scripts\activate
python -m pip install --upgrade pip 
```
2. pytorch 2.2.2 (有GPU可自己安裝CUDA的驅動)
 例如我是RTX 4070，我要下載torch就需要輸入
```
pip install torch==2.2.2 torchvision==0.17.2 torchaudio==2.2.2 --index-url https://download.pytorch.org/whl/cu121
```
> pytorch網站版本說明: https://download.pytorch.org/whl/cu121
4. opencv
```
pip install opencv-python
```
6. matplotlib.pyplot
```
pip install matplotlib
```
8. numpy 1.26.4
```
pip install numpy==1.26.4
```
* 複製w1環境到w2:
	先到w1虛擬環境中輸入 pip freeze > requirements.txt
	把requirements.txt中torch那三個+121cuda拿掉，開頭加上
	`--index-url https://download.pytorch.org/whl/cu121`
	接著到w2
```
C:\114torch\w2> python -m venv env
C:\114torch\w2> .\env\Scripts\activate
(env) C:\114torch\w2> pip install -r ..\w1\requirements.txt
```

### 程式碼範例
```python=
#檢查torch版本
# import torch
# import torchvision
# print(torch.__version__)
# print(torchvision.__version__)


#numpy test
# import numpy as np
# a= np.arange(10)
# print(a)


#cv test
# import cv2
# img = cv2.imread("1.jpg")
# img = cv2.resize(img,(400,400))
# cv2.imshow("image",img)
# cv2.waitKey(0)
# cv2.destroyAllWindows()


#繪製隨機資料
# import torch
# import matplotlib.pyplot as plt

# torch.manual_seed(0)
# n = 100   #資料筆數
# x = torch.randn(n,1)
# w_true = torch.tensor([10.0])
# b_true = torch.tensor([3.0])
# y = w_true * x + b_true + torch.randn(n,1)*2.5

# plt.figure()
# plt.plot(x.numpy(),y.numpy(),'o',color='r')
# plt.title('Data(x,y)')
# plt.xlabel('x');plt.xlabel('y')
# plt.tight_layout()
# plt.savefig('tessst.png')
# plt.show()


#torch test
import torch
from torch import nn
class Model(nn.Module):
    def __init__(self,in_dim=1,out_dim=1):
        
        super().__init__()
        self.lin = nn.Linear (in_dim , out_dim)

    def forward(self,x):
        return self.lin(x)

model = Model(1,1)
print(model)


```

### NOTE
* python:直譯式、物件導向
* numpy是python套件
* torch是matrix格式，放進GPU才能做平行運算，因為GPU是matrix運算
* 是經由torch運算，而x跟y都是由torch運算，所以只能用matpltlib中的numpy來跑，而非python本身np
* nn = neuron network
* Model是subclass,繼承nn.Module
* super()是nn.Modul裡的建構子
* 所有nn都須forward
* weight:鍵結值
* X:dimension
* Y:期望值(實際輸出)
* function set:所有y=a*x+b的y，即f1, f2, f3...，將所有x帶進去每個fi，得到的輸出稱yi(預測值)
* ，如x=(x1, x2, x3,...)帶入f1得y1，
* 而fi中error(歐幾里得距離最小)最小的輸出稱為期望值
* loss funciton:找error最小的值，其代表最佳funciton



## w2
### NOTE
![image](https://hackmd.io/_uploads/H1inhqHsgg.png)
> 期望值:![image](https://hackmd.io/_uploads/BJdE1sSoeg.png)
> 
> 預測值![image](https://hackmd.io/_uploads/H1PmJjHjgg.png)
> 
![image](https://hackmd.io/_uploads/r1WAn9Bsex.png)
1. > ![image](https://hackmd.io/_uploads/Byzvgsrogx.png) : 取得最小值時的變數值，代表「讓f(x)變最小的那個 x」
2. > ![image](https://hackmd.io/_uploads/SJokWjBsel.png):找到那個「函數 𝑓」，能讓 loss 𝐿(𝑓)最小。所以𝑓∗ 就是「最佳函數」
3. > ![image](https://hackmd.io/_uploads/By34WsSixe.png):找到那對參數𝑤,𝑏 能讓 loss 最小。所以 𝑤∗,𝑏∗ 就是最佳的權重和偏差。
而最佳化問題的目標就是找到能讓 loss 最小的 𝑤,𝑏


![image](https://hackmd.io/_uploads/SJl1a5Bslg.png)
![image](https://hackmd.io/_uploads/S13kT5Sixg.png)

以MSE為error，需用chain rule偏微
* 超過3D稱高維，(high dimention)
* loss function:透過高維找出函數
* local miniman: 區域極小值，與全域最小值不同
* 極值在切線斜率(微分、gradient)=0，而切線斜率=瞬時斜率=微分，斜率需找兩點，而切線找不到兩點。
* 偏微分:對某部分weight去作微分而已，是partitial微分
* if 兩點很近(趨近於->0)，可以當作一個點
* 微分又稱梯度(Gradiant)，descent:負的，找極值的方法:Gradient Descent(負梯度法)，因為要往斜率反方向找
* loss function需用負偏微分表示，![image](https://hackmd.io/_uploads/BkX1QwAcxe.png)
loss每次更新一梯度(=step)，learning rate會影響更新速率，太大會直接越過極值而震盪=>調低

### HW_1
```python=
# taxi_fare.py
import torch
from torch import nn, optim
from model import Model
import matplotlib.pyplot as plt
import numpy as np
import os

data = np.loadtxt('taxi_fare_training.csv', delimiter=',', skiprows=1)
x = torch.tensor(data[:, 0:1], dtype=torch.float32)  # 行駛距離
y = torch.tensor(data[:, 1:2], dtype=torch.float32)  # 車資

net = Model()
opt = optim.SGD(net.parameters(), lr=0.01)
loss_f = nn.MSELoss()
loss_hist = []

for epoch in range(50):
    y_hat = net(x)
    loss = loss_f(y, y_hat) # loss這個物件有backward函示去執行gradient descent(SGD)
    opt.zero_grad() # 因為backward()會把梯度累加，所以每次要先歸零，否則會影響GD
    loss.backward()
    opt.step()
    loss_hist.append(float(loss.item()))
    print(f'Epoch {epoch}, Loss: {loss.item()}')
    print(loss)

OUT_DIR = "result"
os.makedirs(OUT_DIR, exist_ok=True)

plt.figure()
plt.plot(range(len(loss_hist)), loss_hist, marker='o')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Training Loss')
plt.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
loss_path = os.path.join(OUT_DIR, 'loss.png')
plt.savefig(loss_path, dpi=150)
plt.show()
# plt.close()

net.eval()
with torch.no_grad():
    y_hat = net(x)
arr = np.hstack([
# 固定記憶體不讓GD繼續更動值，GPU的運算在GPU memory，跟CPU不同，所以CPU不知道，GPU要告知CPU他做完了並使GPU清空DRAM中的gradient
    x.detach().cpu().numpy(), 
    y.detach().cpu().numpy(),
    y_hat.detach().cpu().numpy()
])
pred_csv_path = os.path.join(OUT_DIR, 'prediction.csv')
np.savetxt(pred_csv_path, arr, delimiter=',', header='x,y,y_hat', comments='')

idx = x.squeeze(1).argsort() # 未排序
x_sorted = x[idx]
yhat_sorted = y_hat[idx]

plt.figure()
plt.plot(x.detach().cpu().numpy(), y.detach().cpu().numpy(), 'o', alpha=0.6, label='Data(x, y)')
plt.plot(x.detach().cpu().numpy(), y_hat.detach().cpu().numpy(), 'x', alpha=0.8, label='Model output on training x')
plt.plot(x_sorted.detach().cpu().numpy(), yhat_sorted.detach().cpu().numpy(), '-', linewidth=2, label='Connected model outputs')
plt.xlabel('distance_km')
plt.ylabel('fare_ntd')
plt.title('Taxi_Fare_Training Result')
plt.legend()
plt.tight_layout()
scatter_outputs_path = os.path.join(OUT_DIR, 'Taxi_Fare_Training Result.png')
plt.savefig(scatter_outputs_path, dpi=150)
plt.show()
plt.close()
```
```python=
# model.py
import torch
from torch import nn
class Model(nn.Module):
    def __init__(self, in_dim=1, out_dim=1):
        super().__init__()
        self.lin = nn.Linear(in_dim, out_dim)
    def forward(self, x):
        return self.lin(x)
# model = Model(1, 1)
# print(model)
```
### result
![data_with_true_model_outputs](https://hackmd.io/_uploads/rJd9uGriel.png)
![loss](https://hackmd.io/_uploads/SydqOGSixg.png)


### 程式碼 NOTE
* Matplotlib 與 NumPy 都是基於 CPU 的工具，無法直接處理 GPU tensor
* 如果在 GPU tensor 上直接 .numpy()，會報錯
* .detach() 用來切斷計算圖，得到不需要梯度的 tensor，適合做可視化或存檔
* .cpu() 把資料從顯示卡記憶體複製回主記憶體，NumPy 才能讀取
* .numpy() 把 CPU tensor 轉成 NumPy 陣列，Matplotlib 或 CSV 存檔工具才能使用
* 在 GPU 時一定要 .cpu()，否則無法轉 NumPy
* 習慣統一使用 .detach().cpu().numpy()，可保證程式在 CPU/GPU 都能通用且安全
* 100epoch SGD只跑100次，
* GPU的運算在GPU memory，跟CPU不同，所以CPU不知道，GPU要告知CPU他做完了並使GPU清空DRAM中的gradient