# 【Pytorch 深度學習筆記】搞懂什麼是深度學習以及 Pytorch 的基礎 [TOC] 哈囉大家好我是 LukeTseng,感謝您點進本篇筆記,該篇筆記主要配合讀本 《Deep Learning with pytorch》 進行學習,另外透過網路資料作為輔助。本系列筆記是我本人奠基深度學習基礎知識的開始,若文章有誤煩請各位指正,謝謝! ## 深度學習(Deep Learning, DL)是什麼? 深度學習(Deep Learning)是機器學習的一個分支,是一種以人工神經網路為架構、對資料進行表徵學習的演算法。比較通俗一點的說法是說,深度學習可以透過模仿人類大腦的神經網路,讓機器能像人腦一樣進行學習。 至於「深度」這個詞指的是在網路中使用了多層結構,這些多層結構能夠處理更複雜的特徵和模式。 ### 機器學習 vs 深度學習 《Deep Learning with pytorch》這本書中提到深度學習的變革,以及為什麼需要深度學習。 過去機器學習使用的傳統方法高度依賴於特徵工程(feature engineering)這個技術,也就是人工輸入資料的轉換方式,以利於下游演算法產生正確結果。(以下 google 的網站可以讓你玩一下機器學習,玩完後就會知道這句話是什麼意思了:https://teachablemachine.withgoogle.com) 比如說要辨別出手寫的數字,像 0 跟 1,工程師會手動設計出一套過濾器(filters),用於估計圖像中的邊緣方向(direction of edges)。 邊緣方向是什麼呢?邊緣方向是一種用來區分圖像中不同形狀或結構的特徵。以便區分 1(通常是垂直或接近垂直的邊緣)和 0(包含封閉的圓形邊緣)。 在辨別手寫數字的例子呢,另個有用的手動設計特徵可能是計算封閉孔洞(enclosed holes)的數量,因為 0, 8 以及有圈的 2 等數字中都存在此類特徵,可以提高識別率。 最後就是訓練分類器(classifier),這些手動提取的特徵隨後會被饋送給一個 classifier,classifier 會根據這些特徵的分佈來預測正確的數字。 看到這裡呢,所以機器學習他基本上就是要純手動的去輸入資料、轉換特徵,那這樣子效率不是很低嗎?但其實機器學習可以在金融業做到數據分析,或是其它像推薦引擎、天氣預測等應用。 --- 而深度學習則帶來了範式轉移(paradigm shift, 機器學習可稱為舊的範式, 深度學習則是新的範式),它能夠從原始數據中自動學習特徵。 一樣是區分 0, 1 手寫數字的例子,深度學習中的過濾器(filters)會在訓練過程中被迭代地提煉 (refined)。ummm... 也就是用 for 迴圈一直讓他去跑 training 的意思。 訓練過程會使用一個準則(criterion,即損失函數 Loss function),該函數提供一個數值分數來衡量模型輸出與參考數據(期望輸出)之間的差異。訓練的目標是透過逐步修改深度學習機器,讓分數越來越低,以此確保在對於訓練期間未見過的數據也能維持低分數。 深度學習可以根據範例去學習,這種 refined 是透過不斷讓機器去看範例圖像(如 0, 1 的數字照片)及其目標標籤對(pairs of examples and target labels)來實現的。 :::info 標籤對(labeled pairs)是指「輸入資料」和它對應的「正確答案」配對在一起的組合。在機器學習中,標籤(label)是指一個資料樣本的正確輸出或類別,也就是想要模型預測的目標變數。 ::: ### 小結 | | 機器學習 | 深度學習 | | :-- | :-- | :-- | | 特徵提取 | 需要人工設計特徵工程 | 自動從原始數據學習特徵 | | 人工介入 | 需要大量人工指導和調整 | 模型可自主判斷優化 | | 處理數據類型 | 適合結構化數據 | 擅長非結構化數據(圖像、音訊、文本) | | 硬體需求 | 相對較低 | 需要大量數據和算力 | 基本上可以把深度學習看成是機器學習的 Pro 版,但缺點就是需要龐大的 data 去做訓練才會提高準確率。 ## 為什麼要用 Pytorch? Pytorch 他是目前最流行的開源深度學習框架之一,因其易用性、靈活性和強大的功能而廣受歡迎。自 2019 年起,超過 75% 的國際學術論文都使用了 Pytorch,所以就可見 Pytorch 它的強大了。 另外 Pytorch 可以做到部分實時的執行跟測試,而非等待整個 code 跑完,這對於大型深度學習模型來說,完整執行可能需要很長一段時間。所以 Pytorch 是個很好去快速設計原型的平台,而且大幅加速了 debug 的流程。 在書中也提到 Pytorch 是基於 Python 去構建的,所以具有 Pythonic 的風格。說歸說 Pytorch 是基於 Python 去構建的東西,但其實底層還是由許多 C++ 跟 CUDA,因為考量到效能的關係,所以得讓這些程式碼在 GPU 上大規模執行。 總之 Pytorch 還有相當多的優點,僅列上述就已經足夠證明為何需要使用 Pytorch 了。 ## Pytorch 的基礎概念 Pytorch 的基礎概念主要是以下這五個東西所組成:Tensor、Autograd、nn.Module、Optimizers 和 Device。 ### Tensor(張量) ![image](https://hackmd.io/_uploads/rkWZO_QkWx.png) Image Source:https://www.newittrendzzz.com/post/understanding-tensors-in-pytorch-a-comprehensive-guide Tensor 是 PyTorch 的資料結構,它是一種包含多個數值的數學物體,可以視為向量和矩陣的廣義化版本。tensor 支援多維陣列的運算,並可以在 CPU 或 GPU 上進行加速計算。 張量具有三個屬性: - 維度(Dimensionality):0 維是純量、1 維是向量、2 維是矩陣、3 維以上是高維張量。 - 形狀(Shape):每個維度上的大小,例如 (3, 4) 表示 3 行 4 列。 - 資料型態(Dtype):如 `torch.float32`、`torch.int64` 等。 以下是 pytorch 的範例(建議於 colab 或是 jupyter notebook 上面實作): ```python= import torch # 創建不同維度的張量 scalar = torch.tensor(3.14) # 0 維:純量 vector = torch.tensor([1, 2, 3]) # 1 維:向量 matrix = torch.tensor([[1, 2], [3, 4]]) # 2 維:矩陣 # 查看張量屬性 print(matrix.shape) # torch.Size([2, 2]) print(matrix.dtype) # torch.int64 # 查看張量 print(scalar) print(vector) print(matrix) ``` Output: ``` torch.Size([2, 2]) torch.int64 tensor(3.1400) tensor([1, 2, 3]) tensor([[1, 2], [3, 4]]) ``` tensor 跟 numpy 的 ndarray 蠻像、蠻類似的,但差別在於 tensor 可以在 GPU 上執行,而 numpy 只能在 CPU 上執行。 另外 pytorch 也可以直接從 numpy 陣列中創建 tensor: ```python= import torch import numpy as np arr = np.array([[1, 2], [3, 4]]) tensor_from_numpy = torch.from_numpy(arr) print(tensor_from_numpy) ``` Output: ``` tensor([[1, 2], [3, 4]]) ``` 實際上建立一個 tensor 並不只有兩種形式,以下列出所有建立 tensor 的方法: | methods | description | example code | | -------- | -------- | -------- | | `torch.tensor(data)` | 從 Python 列表或 NumPy 陣列建立張量。 | `x = torch.tensor([[1, 2], [3, 4]])` | | `torch.zeros(size)` | 建立一個全為 0 的張量。 | `x = torch.zeros((2, 3))` | | `torch.ones(size)` | 建立一個全為 1 的張量。 | `x = torch.ones((2, 3))` | | `torch.empty(size)` | 建立一個未初始化的張量。 | `x = torch.empty((2, 3))` | | `torch.rand(size)` | 建立一個服從均勻分佈的隨機張量,值在 $[0, 1)$ 。 | `x = torch.rand((2, 3))` | | `torch.randn(size)` | 建立一個服從常態分配的隨機張量,平均值為 0,標準差為 1。 | `x = torch.randn((2, 3))` | | `torch.arange(start, end, step)` | 建立一個一維序列張量,類似 Python 的 range。 | `x = torch.arange(0, 10, 2)` | | `torch.linspace(start, end, steps)` | 建立一個在指定範圍內等間隔的序列張量。 | `x = torch.linspace(0, 1, 5)` | | `torch.eye(size)` | 建立一個單位矩陣(對角線為 1,其他為 0)。 | `x = torch.eye(3)` | | `torch.from_numpy(ndarray)` | 將 NumPy 陣列轉換為張量。 | `x = torch.from_numpy(np.array([1, 2, 3]))` | table from [PyTorch 张量(Tensor) | 菜鸟教程](https://www.runoob.com/pytorch/pytorch-tensor.html) ### Tensor 的屬性 | 屬性名稱 | 說明 | 回傳型態 | 範例 | | :-- | :-- | :-- | :-- | | **shape** 或 **size()** | 張量的維度大小 | torch.Size | `torch.Size([^11][^12])` | | **dtype** | 數據類型 | torch.dtype | `torch.float32`, `torch.int64` | | **device** | 儲存設備(CPU/GPU) | torch.device | `cpu`, `cuda:0` | | **requires_grad** | 是否需要計算梯度 | bool | `True` 或 `False` | | **ndim** | 維度數量 | int | `2`(表示二維張量) | | **numel()** | 元素總數 | int | `12`(3×4 矩陣) | | **data** | 底層數據(不追蹤梯度) | Tensor | 原始數據張量 | | **grad** | 梯度值 | Tensor 或 None | 反向傳播後的梯度 | | **layout** | 內存布局類型 | torch.layout | `torch.strided` | | **T** | 轉置(2D 張量) | Tensor | 行列互換的張量 | | **is_cuda** | 是否在 GPU 上 | bool | `True` 或 `False` | | **dim()** | 獲取張量的維度數 | int | `tensor.dim()` | | **item()** | 取得單元素張量的值 | number | `tensor.item()` | | **is_contiguous()** | 檢查張量是否連續儲存 | bool | `tensor.is_contiguous()` | table from [PyTorch 张量(Tensor) | 菜鸟教程](https://www.runoob.com/pytorch/pytorch-tensor.html) 接下來的範例程式碼都會在 google colab 上執行。 shape or size(): ```python= import torch x = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(x.shape) # torch.Size([2, 3]) print(x.size()) # torch.Size([2, 3]) print(x.size(0)) # 2 (第一個維度的大小) ``` ``` torch.Size([2, 3]) torch.Size([2, 3]) 2 ``` device: ```python= x = torch.tensor([1, 2, 3]) print(x.device) if torch.cuda.is_available(): y = x.to('cuda:0') print(y.device) ``` ``` cpu cuda:0 ``` requires_grad: ```python= x = torch.tensor([1.0, 2.0, 3.0], requires_grad = True) print(x.requires_grad) y = torch.tensor([1.0, 2.0, 3.0]) print(y.requires_grad) y.requires_grad = True # 可手動設定 print(y.requires_grad) ``` ``` True False True ``` requires_grad 的作用是讓 backward 可以決定了該張量是否要加入自動微分(Autograd)機制。 當 `tensor.requires_grad = True` 時,代表該 tensor 在向前運算(forward pass)時,其所有用於產生它的運算都會被記錄下來,之後呼叫 `tensor.backward()` 時,就會自動計算該張量關於其葉張量(leaf tensor)的梯度(gradient)值。 ndim: ```python= x = torch.tensor([1, 2, 3]) print(x.ndim) y = torch.tensor([[1, 2], [3, 4]]) print(y.ndim) z = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) print(z.ndim) ``` ``` 1 2 3 ``` 1 表示 1 維張量,2 表示 2 維張量,以此類推。 numel(): ```python= x = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(x.numel()) y = torch.ones(3, 4, 5) print(y.numel()) ``` ``` 6 60 ``` 這個函數主要用來表示裡面有多少個元素,輸出第一個的 6 就表示 x 裡面有 6 個元素。 data: ```python= x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) y = x * 2 print(y.data) ``` ``` tensor([2., 4., 6.]) ``` grad: ```python= x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) y = (x ** 2).sum() print(x.grad) # None (反向傳播前) y.backward() print(x.grad) # tensor([2., 4., 6.]) (反向傳播後的梯度) ``` ``` None tensor([2., 4., 6.]) ``` layout: ```python= x = torch.tensor([[1, 2], [3, 4]]) print(x.layout) ``` ``` torch.strided ``` `torch.strided` 為預設的密集儲存格式,若遇到稀疏 tensor 會有不同的 layout。 T: ```python= x = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(x.T) ``` ``` tensor([[1, 4], [2, 5], [3, 6]]) ``` T 只適用於 2D 張量,對於高維張量需用 `permute()` 或 `transpose()` 函數。 is_cuda: ```python= x = torch.tensor([1, 2, 3]) print(x.is_cuda) if torch.cuda.is_available(): y = x.cuda() print(y.is_cuda) ``` ``` False True ``` dim(): ```python= x = torch.tensor([1, 2, 3]) print(x.dim()) y = torch.tensor([[1, 2], [3, 4]]) print(y.dim()) z = torch.ones(2, 3, 4) print(z.dim()) ``` ``` 1 2 3 ``` ndim 跟 dim() 使用上是沒什麼差別的,差別在於 ndim 是比較新的版本才出來的,他是屬於屬性的部分,而非方法。 item(): ```python= x = torch.tensor([42]) print(x.item()) y = torch.tensor([3.14]) print(y.item()) # 只能用於單元素張量,多元素會報錯 ``` ``` 42 3.140000104904175 ``` is_contiguous(): ```python= x = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(x.is_contiguous()) # True (原始張量是連續的) y = x.transpose(0, 1) print(y.is_contiguous()) # False (轉置後不連續) z = y.contiguous() print(z.is_contiguous()) # True (使用 contiguous() 使其連續) ``` ### Tensor 的操作 數學運算操作: | 操作 | 功能說明 | 語法範例 | 說明 | | :-- | :-- | :-- | :-- | | **加法** | 元素相加 | `x + y` 或 `torch.add(x, y)` | 對應位置相加 | | **減法** | 元素相減 | `x - y` 或 `torch.sub(x, y)` | 對應位置相減 | | **乘法(元素)** | 元素相乘 | `x * y` 或 `torch.mul(x, y)` | Hadamard 積 | | **除法** | 元素相除 | `x / y` 或 `torch.div(x, y)` | 對應位置相除 | | **矩陣乘法** | 矩陣相乘 | `x @ y` 或 `torch.mm(x, y)` | 線性代數的矩陣乘法 | | **matmul()** | 多維矩陣乘法 | `torch.matmul(x, y)` | 支援批次矩陣乘法 | | **pow()** | 次方運算 | `x.pow(2)` 或 `x ** 2` | 每個元素平方 | | **sqrt()** | 平方根 | `torch.sqrt(x)` | 每個元素開根號 | | **exp()** | 指數函數 | `torch.exp(x)` | $e^x$ | | **log()** | 自然對數 | `torch.log(x)` | $\ln(x)$ | | **abs()** | 絕對值 | `torch.abs(x)` | 每個元素取絕對值 | | **sin/cos/tan** | 三角函數 | `torch.sin(x)` | 三角運算 | 統計操作: | 操作 | 功能說明 | 語法範例 | 說明 | | :-- | :-- | :-- | :-- | | **sum()** | 求和 | `x.sum()` 或 `x.sum(dim=0)` | 可指定維度 | | **mean()** | 平均值 | `x.mean()` 或 `x.mean(dim=1)` | 需要浮點數類型 | | **max()** | 最大值 | `x.max()` 或 `x.max(dim=0)` | 回傳值和索引 | | **min()** | 最小值 | `x.min()` | 最小元素 | | **argmax()** | 最大值索引 | `x.argmax(dim=1)` | 回傳最大值位置 | | **argmin()** | 最小值索引 | `x.argmin()` | 回傳最小值位置 | | **std()** | 標準差 | `x.std()` | 衡量數據分散程度 | | **var()** | 變異數 | `x.var()` | 標準差的平方 | | **median()** | 中位數 | `torch.median(x)` | 中間值 | 比較操作: | 操作 | 功能說明 | 語法範例 | 返回結果 | | :-- | :-- | :-- | :-- | | **大於** | 元素比較 | `x > y` 或 `torch.gt(x, y)` | 布林張量 | | **小於** | 元素比較 | `x < y` 或 `torch.lt(x, y)` | 布林張量 | | **等於** | 元素比較 | `x == y` 或 `torch.eq(x, y)` | 布林張量 | | **大於等於** | 元素比較 | `x >= y` 或 `torch.ge(x, y)` | 布林張量 | | **小於等於** | 元素比較 | `x <= y` 或 `torch.le(x, y)` | 布林張量 | | **equal()** | 完全相等 | `torch.equal(x, y)` | 單一布林值 | | **allclose()** | 近似相等 | `torch.allclose(x, y)` | 考慮浮點誤差 | 形狀變換操作: | 操作 | 功能說明 | 語法範例 | 實際例子 | | :-- | :-- | :-- | :-- | | **reshape()** | 改變形狀(複製數據) | `x.reshape(3, 4)` | 把 12 個元素重排成 3×4 | | **view()** | 改變形狀(共享記憶體) | `x.view(2, -1)` | `-1` 表示自動計算 | | **squeeze()** | 移除大小為 1 的維度 | `x.squeeze()` | `(1, 3, 1, 4)` → `(3, 4)` | | **unsqueeze()** | 增加維度 | `x.unsqueeze(0)` | `(3, 4)` → `(1, 3, 4)` | | **transpose()** | 交換兩個維度 | `x.transpose(0, 1)` | 交換第 0 和第 1 維 | | **permute()** | 重新排列所有維度 | `x.permute(2, 0, 1)` | 將維度重新排序 | | **flatten()** | 展平為一維 | `x.flatten()` | 所有元素變成一維向量 | | **expand()** | 擴展張量(不複製) | `x.expand(3, -1)` | 廣播機制擴展 | | **repeat()** | 重複張量(複製) | `x.repeat(2, 3)` | 沿各維度重複 | ### 在 tensor 上做 GPU 加速 在前面的範例有稍微介紹到 GPU 加速的部分,這邊額外講解。 要在做 GPU 加速之前,需要透過函數 `torch.cuda.is_available()` 確認是否有可用的 cuda GPU。 `torch.cuda.device_count()` 可查看 GPU 數量。 `torch.cuda.get_device_name(0)`查看 GPU 名稱。 做 GPU 加速的第一個方式是用 `.cuda()` 方法: ```python= x = torch.tensor([1.0, 2.0, 3.0]) print(x.device) # cpu # 移動到 GPU(回傳新的 tensor) x_gpu = x.cuda() print(x_gpu.device) # cuda:0 # 或者重新賦值給自己 x = x.cuda() print(x.is_cuda) # True # 指定特定的 GPU(如果有多張顯卡) x = x.cuda(0) # 使用第一張 GPU ``` ``` cpu cuda:0 True ``` 第二個方式比較推薦用,是使用 `.to(device)` 方法: ```python= # 定義設備(自動選擇 GPU 或 CPU) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(device) # cuda:0 或 cpu # 建立 tensor 並移動到指定設備 x = torch.tensor([1.0, 2.0, 3.0]) x = x.to(device) print(x.device) # cuda:0 # 指定特定 GPU device = torch.device("cuda:0") # 第一張 GPU x = x.to(device) ``` 最後是比較 CPU 與 GPU 間的速度的小範例: ```python= import time # 創建大型矩陣 x = torch.randn(10000, 10000) y = torch.randn(10000, 10000) # CPU 計算時間 start = time.time() z_cpu = x @ y # 矩陣乘法 print(f"CPU 時間: {time.time() - start:.4f} 秒") # GPU 計算時間 x_gpu = x.cuda() y_gpu = y.cuda() torch.cuda.synchronize() # 確保 GPU 操作完成 start = time.time() z_gpu = x_gpu @ y_gpu torch.cuda.synchronize() print(f"GPU 時間: {time.time() - start:.4f} 秒") ``` ``` CPU 時間: 24.7626 秒 GPU 時間: 0.5167 秒 ``` 所以知道為什麼要用 GPU 了吧 XD,尤其日後當層數與參數量大起來的時候,就特別需要 GPU 做運算了。 ## 總結 ### PyTorch 的五大基礎概念 #### Tensor(張量) PyTorch 的核心資料結構,為多維陣列的泛化形式(類似 NumPy ndarray),可在 CPU 或 GPU 上運算。 * 0 維 → 純量(scalar) * 1 維 → 向量(vector) * 2 維 → 矩陣(matrix) * 3 維以上 → 高維張量 #### Autograd(自動微分) 透過 `requires_grad=True` 追蹤張量的運算過程,反向傳播時自動計算梯度(gradient)。 #### nn.Module(神經網路模組) 建構深度學習模型的基底類別,可封裝多層結構(如 CNN、RNN)。 #### Optimizers(優化器) 用於更新模型參數(如 SGD、Adam),以降低損失函數。 #### Device(裝置) 控制資料與模型運算位置(cpu 或 cuda)。 ### Tensor 操作 1. 建立張量的方法 如 torch.tensor()、torch.zeros()、torch.ones()、torch.rand()、torch.eye() 等,支援從列表或 NumPy 陣列建立。 2. 張量屬性 * shape / size():張量形狀 * dtype:資料型態 * device:儲存裝置 * requires_grad:是否參與梯度計算 * grad:梯度值 * is_cuda:是否位於 GPU 3. 常用運算 * 數學運算(加減乘除、平方、log、sin 等)、 * 統計運算(sum、mean、std、var 等)、 * 比較運算(==, >, torch.eq() 等)。 4. 張量形狀操作 reshape()、view()、squeeze()、unsqueeze()、transpose()、permute()、flatten()、expand()、repeat() 等。 ### GPU 加速運算 在深度學習中,GPU 能顯著加速矩陣與張量運算。 主要有兩種方式將資料移動到 GPU: * `.cuda()`:直接將 tensor 移到 GPU。 * `.to(device)`:推薦用法,可自動偵測 CPU/GPU。 ## 參考資料 書籍《Deep Learning with PyTorch》。 [PyTorch documentation — PyTorch 2.9 documentation](https://docs.pytorch.org/docs/stable/index.html) [关于tensor.dim() 和 tensor.ndim-CSDN博客](https://blog.csdn.net/qq_43701910/article/details/142877262) [Pytorch学习: 张量基础操作 | DaNing的博客](https://adaning.github.io/posts/42255.html) [[PyTroch系列-10]:PyTorch基础 - 张量Tensor元素的比较运算_51CTO博客_pytorch中的张量](https://blog.51cto.com/u_11299290/3312993) [numpy & pytotch tensor 常用操作对比_to(dtype).clone().detach().contiguous()-CSDN博客](https://blog.csdn.net/zack_liu/article/details/132280550) [cuda pytorch 加速 pytorch怎么用gpu加速_mob6454cc75556b的技术博客_51CTO博客](https://blog.51cto.com/u_16099306/6828627) [PyTorch 笔记(19)— Tensor 用 GPU 加速_tensor.cuda()-CSDN博客](https://blog.csdn.net/wohu1104/article/details/107239936) [PyTorch 基础 | 菜鸟教程](https://www.runoob.com/pytorch/pytorch-basic.html) [PyTorch 张量(Tensor) | 菜鸟教程](https://www.runoob.com/pytorch/pytorch-tensor.html)