# 【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 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)