---
# System prepended metadata

title: 【Pytorch 深度學習筆記】搞懂什麼是深度學習以及 Pytorch 的基礎
tags: [python, 程式設計, DL, Python, 深度學習, 程式語言, AI]

---

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