# 【Hung-yi Lee 機器學習 - Pytorch】 :::info - colab 環境設置 - Pytorch Basic - Training & Testing Neural Networks - *Python3 自學資源 [w3schools](https://www.w3schools.com/python/)、[The Python Tutorial](https://docs.python.org/3/tutorial/)、[Pytorch](https://pytorch.org/docs/stable/) ::: <br/> ## colab 環境設置 ```= !pip install torch torchvision torch.cuda.is_available() !nvidia-smi ``` ![截圖 2025-02-16 中午12.06.01](https://hackmd.io/_uploads/HkIHVyy91g.png) ```= # 連上cpu gpu # cpu device = torch.device('cpu') x = torch.ones(3, 3).to(device) print(x.device) # gpu # device = torch.device('cuda') # x = torch.ones(3, 3).to(device) # print(x.device) ``` 連上google drive ```= # connect google drive from google.colab import drive drive.mount('/content/drive') ``` 下載檔案 ```= !pip install gdown # 確保 gdown 安裝 !gdown --id 11QTOy1UQxAQzfEjytvsT9pepNF8OqxMH --output train_1.jpg # !gdown --folder --id 1pJkjKvQqWM_3KvZAfXV_gYiY8Qeq3qj6. # 印出folder檔案 # !ls -l /content/ ``` ![截圖 2025-02-16 中午12.07.48](https://hackmd.io/_uploads/SJ-nNykc1x.png) 設置自動點擊 ```= # https://stackoverflow.com/questions/57113226/how-can-i-prevent-google-colab-from-disconnecting?newreg=193c21864d6347c8859f7be882622399 # 設置開發人員console自動點擊 function ConnectButton(){ console.log("Connect pushed"); document.querySelector("#top-toolbar > colab-connectbutton").shadowRoot.querySelector("#connect").click() } setInterval(ConnectButton, 300000); ``` ![截圖 2025-02-16 中午12.36.45](https://hackmd.io/_uploads/ryk9oy19Jl.png) <br/> ## Pytorch Basic ![截圖 2025-02-16 下午2.53.11](https://hackmd.io/_uploads/ryKOo-k5yl.png) ![截圖 2025-02-16 下午2.53.23](https://hackmd.io/_uploads/HknuiWyc1x.png) ```= # 合併不同形狀的張量 # 沿著維度 1 進行拼接 x = torch.zeros([2, 1, 3]) y = torch.zeros([2, 3, 3]) z = torch.zeros([2, 2, 3]) w = torch.cat([x, y, z], dim=1) print(w.shape) print(w.dtype) ``` ![截圖 2025-02-22 下午1.26.23](https://hackmd.io/_uploads/ry_fxkP51l.png) ```= # 將維度數量減少 reshaped_w = w.reshape(2, 18) print(reshaped_w.shape) viewed_w = w.view(3, 12) print(viewed_w.shape) ``` ```= # 移除所有維度為 1 的軸 squeezed_x = x.squeeze() print(squeezed_x.shape) ``` ```= # 在指定的維度 (這裡是第 1 維) 插入一個新的大小為 1 的維度 x = torch.zeros([2, 3]) unsqueezed_x = x.unsqueeze(1) print(unsqueezed_x.shape) ``` ![截圖 2025-02-16 下午3.04.58](https://hackmd.io/_uploads/BJi70b15Jg.png) ```= # 1 2 維數據交換 z = torch.tensor([ [1, 2, 3], [4, 5, 6] ]) print("原始 z:") print(z) print("z.shape:", z.shape) print() z2 = z.transpose(0, 1) print("交換後的 z2:") print(z2) print("z2.shape:", z2.shape) ``` ![截圖 2025-02-22 下午1.00.41](https://hackmd.io/_uploads/SJt-9AL5ye.png) ```= # 移除張量中指定維度為 1 的軸 # 計算機視覺 (CNN) (1, C, H, W) squeeze(0) (C, H, W) # NLP (RNN, Transformer) (batch, 1, seq_len) squeeze(1) (batch, seq_len) z4 = torch.tensor([[[1, 2, 3, 4], [5, 1, 7, 8], [9, 10, 11, 12]]]) z5 = z4.squeeze(0) print(z4) print() print(z4.shape) print() print(z5) print() print(z5.shape) print() ``` ![截圖 2025-02-22 下午1.04.09](https://hackmd.io/_uploads/By-1iRU5Jg.png) ```= # numpy 需要共享記憶體、減少複製 np_arr = np.array([[1, -1], [-1, 1]], dtype=np.float32) x2 = torch.from_numpy(np_arr) x2[0, 0] = 99 print(np_arr) ``` ```= # torch_tensor 需要獨立的 PyTorch 張量,不影響原數據 torch_tensor = torch.tensor([[1, -1], [-1, 1]]) torch_tensor[0, 0] = 99 print(torch_tensor) ``` ![截圖 2025-02-22 下午1.45.52](https://hackmd.io/_uploads/BkZi41Dqkx.png) ```= # numpy import numpy as np np_arr = np.array([[1, -1, 3, 4], [-1, 1, 99, 2]], dtype=np.float32) # 在第一維(索引 2)增加一個新維度 np_arr_2 = np.expand_dims(np_arr, 2) print(np_arr) print(np_arr.shape) print() print(np_arr_2) print(np_arr_2.shape) ``` ![截圖 2025-02-22 下午1.48.38](https://hackmd.io/_uploads/rJwSS1w9Jx.png) 計算張量梯度 ```= # 計算張量梯度 x = torch.tensor([[1., 0.], [-1., 1.]], requires_grad=True) print(x) print() z0 = x.pow(2) # 平方 print(z0) print() z = x.pow(2).sum() # z = (1^2 + 0^2 + (-1)^2 + 1^2) = 1 + 0 + 1 + 1 = 3 print(z) print() # 反向計算 z1 = z.backward() print(z1) print() print(x.grad) # ∂z/∂x[0,0] = 2 * x[0,0] = 2 * 1 = 2 # ∂z/∂x[0,1] = 2 * x[0,1] = 2 * 0 = 0 # ∂z/∂x[1,0] = 2 * x[1,0] = 2 * -1 = -2 # ∂z/∂x[1,1] = 2 * x[1,1] = 2 * 1 = 2 ``` ![截圖 2025-02-16 下午4.50.38](https://hackmd.io/_uploads/H1jgwXk51x.png) ```= x = torch.tensor([[2., 0.], [-2., 1.]], requires_grad=True) print(x) print() z0 = x.pow(4) print(z0) print() z = x.pow(4).sum() # z = (2^4 + 0^4 + (-2)^4 + 1^4) = 16 + 0 +16 + 1 = 1 print(z) print() # 反向計算 # backward() 方法會根據 z 對 x 求導數,並且會把結果存儲在 x.grad z1 = z.backward() print(z1) print() print(x.grad) # 梯度計算,冪次法則 # ∂z/∂x[0,0] = 4 * x[0,0]^2 = 4 * 2^3 = 32 # ∂z/∂x[0,1] = 4 * x[0,1]^2 = 4 * 0^3 = 0 # ∂z/∂x[1,0] = 4 * x[1,0]^2 = 4 * (-2)^3 = -32 # ∂z/∂x[1,1] = 4 * x[1,1]^2 = 4 * 1^3 = 4 ``` ![截圖 2025-02-22 下午2.19.36](https://hackmd.io/_uploads/SJ2YnJv5yx.png) <br/> # Training & Testing Neural Networks ```= import torch import torch.nn as nn ``` ```= # 定義線性層 # 輸入32 輸出64 layer = torch.nn.Linear(32, 64) print(layer.weight.shape) print() print(layer.bias.shape) ``` ![截圖 2025-02-22 下午2.46.03](https://hackmd.io/_uploads/HklaflPqkl.png) ```= # nn.Sigmoid() # 將線性層的輸出轉換成非線性轉換輸出 # nn.Sigmoid(): 二分類問題中的輸出層,用來將模型的預測值轉換為 0 和 1 之間的機率值 # 定義 Sigmoid 激活函數 sigmoid = nn.Sigmoid() # 創建一個張量 x = torch.tensor([0.0, 1.0, -1.0, 2.0, -2.0]) # 應用 Sigmoid 函數 output = sigmoid(x) print(output) ``` ![截圖 2025-02-22 下午2.48.31](https://hackmd.io/_uploads/SkkLQlP9kx.png) ```= # nn.ReLU(): 用於隱藏層,可加速收斂並減少梯度消失,廣泛應用於深度神經網絡 # 但是,當輸入小於 0 時,梯度會是 0,這可能會導致「死區」問題 # 定義 ReLU 激活函數 relu = nn.ReLU() # 創建一個張量 x = torch.tensor([0.0, 1.0, -1.0, 2.0, -2.0]) # 應用 ReLU 函數 output = relu(x) print(output) ``` ![截圖 2025-02-22 下午2.54.06](https://hackmd.io/_uploads/SJR94lw9Je.png) ```= # 建立神經網絡的模組 # 寫法一 class MyModel(nn.Module): def __init__(self): # super() 語句來呼叫父類的初始化方法 super(MyModel, self).__init__() self.net = nn.Sequential( nn.Linear(3, 16), # 從 3 欄位 (a, b, c) 到 16 欄位 nn.ReLU(), # 使用 ReLU 激活函數 nn.Linear(16, 3) # 從 16 欄位到 3 欄位 (d, e, f) ) # 前向傳播方法,接收輸入 x 並將其傳遞通過 self.net 來計算輸出 def forward(self, x): return self.net(x) ``` ```= # 寫法二 # 當模型需要更多的控制,或是需要根據條件進行不同的操作(如跳躍連接、不同激活函數等),這種顯式定義方法提供了更多靈活性 class MyModel(nn.Module): def __init__(self): super(MyModel, self).__init__() self.layer1 = nn.Linear(10, 32) self.layer2 = nn.Sigmoid(), self.layer3 = nn.Linear(32,1) def forward(self, x): out = self.layer1(x) out = self.layer2(out) out = self.layer3(out) return out ``` ```= # 均方誤差 (Mean Squared Error, MSE) criterion = nn.MSELoss() # 交叉熵 (Cross Entropy):適用於分類任務。它用來衡量預測的機率分佈與實際標籤之間的差異 criterion = nn.CrossEntropyLoss() # 計算損失:在獲得模型的輸出後,可以使用損失函數來計算預測值與期望值之間的損失 loss = criterion(model_output, expected_value) ``` ps reference [類神經網路訓練不起來怎麼辦 (三)](https://www.youtube.com/watch?v=HYUXEeh3kwY) ```= # 隨機梯度下降 (Stochastic Gradient Descent, SGD) # torch.optim 中的優化算法可以幫助你有效地訓練神經網絡,通過調整參數來最小化損失 # 類神經網路訓練不起來怎麼辦 (三) https://www.youtube.com/watch?v=HYUXEeh3kwY import torch.optim as optim # 假設模型已經定義為 model,ir 學習率,momentum 動量 optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0) ``` ```= # 重置梯度: optimizer.zero_grad() 清除模型參數的梯度 # 因為 PyTorch 默認會累加梯度,如果不重置,會導致梯度累加,影響訓練效果 optimizer.zero_grad() ``` ```= # 反向傳播: 使用 loss.backward() 來計算預測損失的梯度 # 會根據損失函數的值計算出每個參數對於損失的貢獻 loss.backward() ``` ```= # 更新模型參數: optimizer.step() 根據計算出的梯度來調整模型的參數 # 優化器就會根據梯度下降規則更新每個參數的值,以期減少損失函數的值 optimizer.step() ``` setup ```= # setup import torch import torch.nn as nn import torch.optim as optim class MyDataset(torch.utils.data.Dataset): # def __init__(self, file): # # 初始化時讀取數據 # self.data = ... # def __len__(self): # return len(self.data) # def __getitem__(self, idx): # # 獲取指定索引的數據 # a = self.data[idx]['a'] # b = self.data[idx]['b'] # c = self.data[idx]['c'] # d = self.data[idx]['d'] # f = self.data[idx]['f'] # # 返回輸入 (a, b, c) 和輸出 (c, d, f) # x = torch.tensor([a, b, c], dtype=torch.float32) # y = torch.tensor([c, d, f], dtype=torch.float32) # return x, y # 導入文件 file = r"" dataset = MyDataset(file) # 使用 DataLoader 將數據集包裝起來,設置每個批次的大小為 16,並且在每個 epoch 開始前隨機打亂數據 tr_set = DataLoader(dataset, 16, shuffle=True) # 初始化模型,移動到(gpu, cpu) # model = MyModel().to(device) # cpu cuda # 均方誤差 (Mean Squared Error, MSE) criterion = nn.MSELoss() # 測試不同的學習率優化器 learning_rates = [0.001, 0.01, 0.1] best_loss = float('inf') best_lr = None for lr in learning_rates: optimizer = optim.SGD(model.parameters(), lr=lr) # 迭代訓練epoch for epoch in range(100): # 訓練 100 個 epoch model.train() # 設置模型為訓練模式 for x, y in tr_set: # 迭代數據加載器 optimizer.zero_grad() # 清除之前的梯度 x, y = x.to(device), y.to(device) # cpu cuda pred = model(x) # 前向傳播,獲取預測結果 loss = criterion(pred, y) loss.backward() # 反向傳播計算梯度 optimizer.step() # 更新模型 # 檢查損失 if loss.item() < best_loss: best_loss = loss.item() best_lr = lr print(f"最佳學習率: {best_lr}, 最小化的損失: {best_loss}") ``` 計算損失 ```= # 神經網絡驗證循環 # 平均損失 model.eval() # 初始化總損失 total_loss = 0 # 驗證集 for x, y in dv_set: x, y = x.to(device), y.to(device) # 禁用梯度計算,不進行反向傳播和參數更新 with torch.no_grad(): # 前向傳播計算輸出 pred = model(x) # 計算損失 loss = criterion(pred, y) # 累積損失 total_loss += loss.cpu().item() * len(x) # 計算平均損失 avg_loss = total_loss / len(dv_set.dataset) ``` 收集預測結果 ```= # 將預測結果轉回 CPU,添加到 preds 列表中,以便後續分析或評估 model.eval() # 初始化預測列表 pred = [] for x in tt_set: x = x.to(device) # 禁用梯度計算 with torch.no_grad(): # 前向傳播計算輸出 pred = model(x) # 收集預測結果 preds.append(pred.cpu()) ``` 儲存模型的權重 ```= # torch.save() 保存模型的權重,保存的權重未來可以重新加載模型 # model.state_dict() 會返回模型的所有可學習參數的字典 torch.save(model.state_dict(), 'model_weights.pth') ``` 新資料 重新加載模型 ```= # 新資料 # 加載已訓練的模型 model.load_state_dict(torch.load('model_weights.pth')) model.eval() # 準備新數據 class MyDataset(torch.utils.data.Dataset): def __init__(self, file): # 初始化時讀取數據 self.data = ... def __len__(self): return len(self.data) def __getitem__(self, idx): # 獲取指定索引的數據 a = self.data[idx]['a'] b = self.data[idx]['b'] c = self.data[idx]['c'] # 返回輸入 (a, b, c) 和輸出 (c, d, f) inputs = torch.tensor([a, b, c], dtype=torch.float32) return inputs new_input_data = inputs new_input_tensor = torch.tensor(new_input_data, dtype=torch.float32).to(device) # 禁用梯度計算並進行預測 with torch.no_grad(): prediction = model(new_input_tensor) # 將預測結果轉回 CPU prediction = prediction.cpu().numpy() # print(prediction) ``` torch.max() ```= # max import torch x = torch.tensor([[1, 5, 3], [7, 2, 8]]) max_value = torch.max(x) print(max_value) ``` ```= x = torch.tensor([[1, 5, 3], [7, 2, 8]]) max_values, indices = torch.max(x, dim=1) # 0 1 列 行 print(max_values) # tensor([5, 8]) -> 每行的最大值 print(indices) # tensor([1, 2]) -> 最大值的索引 ``` ```= x = torch.tensor([[1, 5, 3], [7, 2, 8]]) max_values, indices = torch.max(x, dim=0) # 0 1 列 行 print(max_values) print(indices) ``` ```= x = torch.tensor([[1, 5, 3], [7, 2, 8]]) y = torch.tensor([[3, 2, 4], [5, 6, 1]]) result = torch.max(x, y) print(result) # tensor([[3, 5, 4], # [7, 6, 8]]) # 逐元素選取較大的值 ``` ![截圖 2025-02-23 下午3.10.41](https://hackmd.io/_uploads/H1XW5Bu5kx.png)