PyTorch 系列:CNN 模型
這是 Deep Learning 系列的其第二篇。Convolutional Neural Networks (CNNs,卷積神經網絡) 是深度學習領域中一個非常重要的分支,尤其在電腦視覺 (Computer Vision) 任務中取得了巨大的成功。PyTorch 作為一個靈活且強大的深度學習框架,為研究和應用 CNN 提供了極大的便利。本文將帶你一步步了解 CNN 的基本原理、核心組件,並通過 PyTorch 實戰一個圖像分類任務。
📖 1. 什麼是卷積神經網絡 (CNN)?
卷積神經網絡(CNN)是一種模仿人類視覺處理機制的深度學習模型,擅長從圖像或時間序列等具有空間結構的數據中自動提取「由簡入繁」的特徵。它通過層層堆疊的運算單元,逐步從原始數據中學習邊緣、紋理等基礎特徵,並逐級組合成更複雜的局部與整體特徵,最終實現對數據的高效理解與分類。
1.1 CNN 的核心思想
CNN 的強大能力主要歸功於以下幾個核心思想:
- 局部連接 (Local Connectivity):傳統神經網絡中,每一層的神經元都與前一層的所有神經元相連。但在 CNN 中,卷積層的神經元只與前一層的一個局部區域相連。這個局部區域就是感受野。這大大減少了模型的參數數量,使得訓練大型網絡成為可能。
- 權重共享 (Weight Sharing):在一個卷積層中,一個特定的濾波器 (filter) 或核心 (kernel) 會掃描整個輸入圖像,濾波器內的權重在掃描過程中是共享的。這意味著模型可以在圖像的不同位置檢測到相同的特徵,進一步減少了參數數量,並使模型具有平移不變性 (translation invariance)。
- 層級特徵 (Hierarchical Features):CNN 通常由多個層堆疊而成。淺層的卷積層學習低級特徵,如邊緣、角點和顏色。隨著網絡層次的加深,更深層的卷積層會基於淺層的特徵組合成更複雜、更抽象的高級特徵,例如物體的部件或整個物體。
1.2 CNN 的典型應用
CNN 的應用非常廣泛,尤其在電腦視覺領域:
- 圖像分類 (Image Classification):判斷圖像屬於哪個預定義的類別 (e.g., 貓、狗、汽車)。
- 目標檢測 (Object Detection):在圖像中定位並識別出多個物體。
- 圖像分割 (Image Segmentation):將圖像中的每個像素分配到一個類別,實現像素級的分類。
- 人臉識別 (Face Recognition):識別或驗證圖像中的人臉。
- 圖像生成 (Image Generation):例如使用生成對抗網絡 (GANs) 生成新的圖像。
🛠️ 2. CNN 的核心組件
一個典型的 CNN 架構主要由卷積層、池化層和全連接層構成。
2.1 卷積層 (Convolutional Layer)
卷積層是 CNN 的核心。它通過在輸入數據上滑動一個或多個濾波器 (filters) 或核心 (kernels) 來執行卷積運算,從而提取特徵。
- 輸入 (Input):通常是一個三維張量 (height × width × channels)。例如,一張彩色圖像的輸入通道數為 3 (RGB)。
- 濾波器/核心 (Filter/Kernel):一個小的權重矩陣,其深度與輸入數據的通道數相同。濾波器的作用是檢測特定的局部特徵,例如邊緣、角點或紋理。一個卷積層可以有多個濾波器,每個濾波器學習檢測不同的特徵。
- 步長 (Stride):濾波器在輸入數據上每次移動的像素數量。較大的步長會導致輸出特徵圖 (feature map) 的尺寸變小。
- 填充 (Padding):在輸入數據的邊緣周圍添加額外的像素 (通常是 0)。填充可以幫助控制輸出特徵圖的空間尺寸,並減少邊緣信息的丟失。常用的有 'valid' (不填充) 和 'same' (填充使得輸出尺寸與輸入尺寸在忽略步長影響時相同)。
- 輸出通道 (Output Channels):卷積層中濾波器的數量決定了輸出特徵圖的通道數 (或深度)。每個濾波器產生一個特徵圖。
- 激活函數 (Activation Function):卷積運算後,通常會應用一個非線性激活函數,如 ReLU (Rectified Linear Unit)。ReLU 的計算公式是
f(x) = max(0, x)
,它能引入非線性,並有助於緩解梯度消失問題。
卷積運算示意圖 (Conceptual Convolution Operation):
假設我們有一個 5x5 的輸入圖像 (單通道) 和一個 3x3 的濾波器,步長為 1,無填充。
Input (5x5) Kernel (3x3) Output (3x3)
I11 I12 I13 I14 I15 K11 K12 K13 O11 O12 O13
I21 I22 I23 I24 I25 K21 K22 K23 -> O21 O22 O23
I31 I32 I33 I34 I35 K31 K32 K33 O31 O32 O33
I41 I42 I43 I44 I45
I51 I52 I53 I54 I55
O11 = I11*K11 + I12*K12 + I13*K13 +
I21*K21 + I22*K22 + I23*K23 +
I31*K31 + I32*K32 + I33*K33
(然後濾波器向右移動一個步長,計算 O12,以此類推)
PyTorch 實現 (torch.nn.Conv2d
):
torch.nn.Conv2d
用於處理 2D 數據 (如圖像)。其主要參數包括:
in_channels
: 輸入通道數。out_channels
: 輸出通道數 (即濾波器的數量)。kernel_size
: 濾波器的大小 (可以是一個整數或一個元組(height, width)
)。stride
: 步長 (預設為 1)。padding
: 填充大小 (預設為 0)。
import torch
import torch.nn as nn
# 假設輸入是一個 batch size 為 1,3 個通道 (RGB),高度和寬度為 32x32 的圖像
dummy_input = torch.randn(1, 3, 32, 32)
# 定義一個卷積層
# 輸入通道 3, 輸出通道 16, 核心大小 3x3, 步長 1, 填充 1
conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
# 應用卷積層
output_feature_map = conv1(dummy_input)
print("輸入尺寸:", dummy_input.shape) # torch.Size([1, 3, 32, 32])
print("輸出特徵圖尺寸:", output_feature_map.shape) # torch.Size([1, 16, 32, 32])
# 因為 padding=1, stride=1, kernel_size=3,所以輸出尺寸保持不變:
# H_out = (H_in - K_h + 2*P)/S + 1 = (32 - 3 + 2*1)/1 + 1 = 32
2.2 池化層 (Pooling Layer)
池化層通常緊跟在卷積層之後,用於降低特徵圖的空間維度 (高度和寬度),從而:
- 減少後續層的計算量。
- 減少參數數量,有助於防止過度擬合 (overfitting)。
- 使模型對輸入圖像中的微小位移和平移具有一定的不變性。
最常見的池化操作有:
- 最大池化 (Max Pooling):在池化窗口內選取最大值作為輸出。它傾向於保留最顯著的特徵。
- 平均池化 (Average Pooling):在池化窗口內計算平均值作為輸出。它傾向於平滑特徵。
PyTorch 實現 (torch.nn.MaxPool2d
, torch.nn.AvgPool2d
):
主要參數:
kernel_size
: 池化窗口的大小。stride
: 步長 (預設值通常等於kernel_size
)。padding
: 填充 (預設為 0)。
# 假設輸入特徵圖來自前一個卷積層
# batch size 1, 16 個通道, 高度和寬度為 32x32
feature_map_from_conv = torch.randn(1, 16, 32, 32)
# 定義一個最大池化層
# 窗口大小 2x2, 步長 2
pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
# 應用池化層
output_pooled_map = pool1(feature_map_from_conv)
print("池化前尺寸:", feature_map_from_conv.shape) # torch.Size([1, 16, 32, 32])
print("池化後尺寸:", output_pooled_map.shape) # torch.Size([1, 16, 16, 16])
# H_out = (H_in - K_h)/S + 1 = (32 - 2)/2 + 1 = 16 (假設padding=0)
2.3 全連接層 (Fully Connected Layer / Dense Layer)
在經過多個卷積層和池化層提取特徵後,通常會將最終的特徵圖展平 (flatten) 成一個一維向量,然後輸入到一個或多個全連接層中。全連接層的作用是將學習到的分佈式特徵表示映射到樣本的標籤空間,進行最終的分類或回歸。
在 PyTorch 中,全連接層由 torch.nn.Linear
實現。
# 假設池化後的特徵圖尺寸為 [1, 16, 7, 7] (batch_size, channels, height, width)
pooled_features = torch.randn(1, 16, 7, 7)
# 展平特徵圖
# -1 表示 batch size 維度保持不變,其餘維度展平成一維
flattened_features = pooled_features.view(-1, 16 * 7 * 7)
print("展平後尺寸:", flattened_features.shape) # torch.Size([1, 784]) (784 = 16*7*7)
# 定義一個全連接層
# 輸入特徵數 16*7*7, 輸出特徵數 128 (例如,隱藏層)
fc1 = nn.Linear(in_features=16 * 7 * 7, out_features=128)
hidden_output = torch.relu(fc1(flattened_features)) # 通常會接一個激活函數
print("第一個全連接層輸出尺寸:", hidden_output.shape) # torch.Size([1, 128])
# 假設是 10 分類問題,定義輸出層
output_layer = nn.Linear(in_features=128, out_features=10)
final_logits = output_layer(hidden_output)
print("最終輸出 Logits 尺寸:", final_logits.shape) # torch.Size([1, 10])
# 這些 logits 通常會經過 Softmax 函數轉換為概率
2.4 其他常用層
批次標準化層 (Batch Normalization Layer):由
torch.nn.BatchNorm2d
(用於2D卷積層後) 或torch.nn.BatchNorm1d
(用於全連接層後) 實現。- 作用:對每一批 (batch) 數據的激活值進行標準化 (使其均值為0,方差為1),然後進行縮放和平移。這有助於:
- 加速模型訓練收斂。
- 提高模型的穩定性,減少對初始化權重的敏感度。
- 在一定程度上起到正則化 (regularization) 的作用。
- 通常放置在卷積層或全連接層之後,激活函數之前。
- 作用:對每一批 (batch) 數據的激活值進行標準化 (使其均值為0,方差為1),然後進行縮放和平移。這有助於:
Dropout 層:由
torch.nn.Dropout
實現。- 作用:在訓練過程中,以一定的概率
p
隨機將輸入張量中的部分元素置為零,其餘元素按1/(1-p)
的比例放大,以保持總體的期望值不變。這是一種非常有效的正則化技巧,用於防止模型過度擬合訓練數據。 - 在模型評估 (evaluation) 或測試 (testing) 時,Dropout 層不起作用 (即所有神經元都參與計算)。
- 作用:在訓練過程中,以一定的概率
🏗️ 3. 經典 CNN 架構概覽
多年來,研究人員提出了許多經典的 CNN 架構,它們在圖像識別等任務上取得了里程碑式的成就。了解這些架構有助於我們更好地設計自己的 CNN 模型。
3.1 LeNet-5 (1998)
- 提出者:Yann LeCun 等人。
- 主要貢獻:最早成功應用於手寫數字識別的 CNN 之一,奠定了現代 CNN 的基礎結構:卷積層 -> 池化層 -> 卷積層 -> 池化層 -> 全連接層 -> 全連接層 -> 輸出層。
- 特點:使用了
sigmoid
或tanh
作為激活函數,平均池化。
3.2 AlexNet (2012)
- 提出者:Alex Krizhevsky, Ilya Sutskever, Geoffrey Hinton。
- 主要貢獻:在 ImageNet LSVRC-2012 圖像分類競賽中以巨大優勢奪冠,引爆了深度學習在電腦視覺領域的熱潮。
- 特點:
- 更深的網絡結構 (5 個卷積層,3 個全連接層)。
- 首次使用 ReLU 作為激活函數,有效解決了梯度消失問題,加速了訓練。
- 使用 Dropout 防止過度擬合。
- 使用數據增強 (Data Augmentation) 擴充訓練集。
- 使用 GPU 進行訓練,大幅提升了計算效率。
3.3 VGGNet (2014)
- 提出者:Karen Simonyan, Andrew Zisserman (Visual Geometry Group, Oxford)。
- 主要貢獻:探索了網絡深度對性能的影響。
- 特點:
- 結構非常規整,全部使用 3x3 的小尺寸卷積核和 2x2 的最大池化層。
- 通過堆疊多個 3x3 卷積核來替代較大的卷積核 (例如,兩個 3x3 卷積層的感受野相當於一個 5x5 卷積層,但參數更少,非線性更強)。
- 常見的有 VGG-16 和 VGG-19 (數字代表帶權重的層數)。
- 網絡較深,參數量大。
3.4 GoogLeNet (Inception v1) (2014)
- 提出者:Google 團隊。
- 主要貢獻:在 ImageNet LSVRC-2014 競賽中獲得冠軍,引入了 Inception Module 的概念。
- 特點:
- Inception Module:在同一層並行使用不同尺寸的卷積核 (1x1, 3x3, 5x5) 和池化操作,然後將它們的輸出拼接起來。這樣可以捕獲不同尺度的特徵。
- 使用 1x1 卷積核進行降維 (bottleneck layer) 和升維,有效減少了計算量和參數數量。
- 網絡更深,但計算效率相對較高。
- 去掉了最後的全連接層,改用全局平均池化 (Global Average Pooling, GAP),大大減少了參數。
3.5 ResNet (Residual Network) (2015)
- 提出者:Kaiming He 等人 (Microsoft Research Asia)。
- 主要貢獻:在 ImageNet LSVRC-2015 競賽中獲得冠軍,通過引入殘差學習 (Residual Learning) 成功訓練了非常深的網絡 (例如 152 層,甚至超過 1000 層)。
- 特點:
- 殘差塊 (Residual Block):核心思想是學習殘差映射
F(x)
而不是直接學習原始映射H(x)
。通過 "捷徑連接" (shortcut connection 或 skip connection),將輸入x
直接加到塊的輸出上,即H(x) = F(x) + x
。 - 解決了深度網絡中的梯度消失/爆炸問題和退化問題 (degradation problem,即網絡越深,性能反而下降)。
- 殘差塊 (Residual Block):核心思想是學習殘差映射
Resnet 結構圖(來源:wikimedia)
* ResNet 有多種變體,如 ResNet-18, ResNet-34, ResNet-50, ResNet-101, ResNet-152。
這些經典模型不僅推動了 CNN 的發展,也為後續的研究提供了重要的思路和基礎。torchvision.models
模塊提供了這些經典模型的預訓練版本,方便我們進行遷移學習。
💻 4. PyTorch 實戰:搭建一個簡單的 CNN 模型進行圖像分類
接下來,我們將使用 PyTorch 搭建一個簡單的 CNN 模型,並在 CIFAR-10 數據集上進行圖像分類任務。CIFAR-10 數據集包含 10 個類別的 32x32 彩色圖像,每個類別有 6000 張圖像 (5000 張訓練,1000 張測試)。
4.1 問題定義 (Problem Definition)
我們的目標是訓練一個 CNN 模型,使其能夠正確識別 CIFAR-10 數據集中的圖像屬於哪一個類別 (飛機、汽車、鳥、貓、鹿、狗、青蛙、馬、船、卡車)。
4.2 數據準備 (Data Preparation)
PyTorch 的 torchvision
庫提供了方便的接口來加載 CIFAR-10 數據集,並進行必要的預處理。
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
# 設定 device (GPU or CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 數據預處理
# 1. 將圖像轉換為 PyTorch 張量 (Tensor)
# 2. 標準化 (Normalize) 圖像數據:對於 RGB 三個通道,分別使用均值 (0.5, 0.5, 0.5) 和標準差 (0.5, 0.5, 0.5)
# 這樣可以將像素值從 [0, 1] 範圍轉換到 [-1, 1] 範圍。
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 下載並加載訓練數據集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, # 增加 batch_size
shuffle=True, num_workers=2)
# 下載並加載測試數據集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=100, # 增加 batch_size
shuffle=False, num_workers=2)
# CIFAR-10 的類別
classes = ('plane', 'car', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
4.3 模型構建 (Model Building)
我們來定義一個簡單的 CNN 模型,它包含兩個卷積層和三個全連接層。
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# 第一個卷積層:輸入通道 3 (RGB),輸出通道 32,核心大小 5x5
self.conv1 = nn.Conv2d(3, 32, kernel_size=5, padding=2) # padding=2 保持尺寸
self.pool = nn.MaxPool2d(kernel_size=2, stride=2) # 32x32 -> 16x16
# 第二個卷積層:輸入通道 32,輸出通道 64,核心大小 5x5
self.conv2 = nn.Conv2d(32, 64, kernel_size=5, padding=2) # padding=2 保持尺寸
# 池化層後,圖像尺寸變為 16x16 (conv1) -> 8x8 (conv2 pool)
# 全連接層
# 64 (channels) * 8 (height) * 8 (width) = 4096
self.fc1 = nn.Linear(64 * 8 * 8, 512)
self.fc2 = nn.Linear(512, 128)
self.fc3 = nn.Linear(128, 10) # 輸出 10 個類別的 logits
def forward(self, x):
# x: [batch_size, 3, 32, 32]
x = self.pool(torch.relu(self.conv1(x))) # -> [batch_size, 32, 16, 16]
x = self.pool(torch.relu(self.conv2(x))) # -> [batch_size, 64, 8, 8]
# 展平操作
x = x.view(-1, 64 * 8 * 8) # -> [batch_size, 4096]
x = torch.relu(self.fc1(x)) # -> [batch_size, 512]
x = torch.relu(self.fc2(x)) # -> [batch_size, 128]
x = self.fc3(x) # -> [batch_size, 10] (logits)
return x
# 實例化模型並將其移動到 device
model = SimpleCNN().to(device)
print(model)
4.4 損失函數與優化器 (Loss Function and Optimizer)
對於多分類問題,我們通常使用交叉熵損失 (Cross-Entropy Loss)。優化器可以選擇 Adam 或 SGD。
# 損失函數
criterion = nn.CrossEntropyLoss() # CrossEntropyLoss 內部已包含 Softmax
# 優化器
optimizer = optim.Adam(model.parameters(), lr=0.001)
# optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
4.5 模型訓練 (Model Training)
訓練過程包括以下步驟:
- 從
trainloader
獲取一批 (batch) 數據。 - 將數據和標籤移動到
device
。 - 將優化器的梯度清零 (
optimizer.zero_grad()
)。 - 前向傳播:將輸入數據傳遞給模型得到輸出 (
outputs = model(inputs)
)。 - 計算損失 (
loss = criterion(outputs, labels)
)。 - 反向傳播:計算梯度 (
loss.backward()
)。 - 更新參數:根據梯度更新模型權重 (
optimizer.step()
)。
num_epochs = 15 # 增加訓練輪數以獲得更好性能
for epoch in range(num_epochs):
running_loss = 0.0
model.train() # 設置模型為訓練模式
for i, data in enumerate(trainloader, 0):
# 獲取輸入;data 是一個 [inputs, labels] 的列表
inputs, labels = data[0].to(device), data[1].to(device)
# 梯度清零
optimizer.zero_grad()
# 前向傳播 + 反向傳播 + 優化
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 打印統計信息
running_loss += loss.item()
if (i + 1) % 200 == 0: # 每 200 個 mini-batches 打印一次
print(f'Epoch [{epoch + 1}/{num_epochs}], Step [{i + 1}/{len(trainloader)}], Loss: {running_loss / 200:.4f}')
running_loss = 0.0
print('Finished Training')
4.6 模型評估 (Model Evaluation)
訓練完成後,我們需要在測試集上評估模型的性能。
model.eval() # 設置模型為評估模式 (這對 Dropout 和 BatchNorm 很重要)
correct = 0
total = 0
# 在評估時,我們不需要計算梯度
with torch.no_grad():
for data in testloader:
images, labels = data[0].to(device), data[1].to(device)
outputs = model(images)
# torch.max 返回 (最大值, 最大值索引)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'Accuracy of the network on the 10000 test images: {accuracy:.2f} %')
# 查看每個類別的準確率
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
for data in testloader:
images, labels = data[0].to(device), data[1].to(device)
outputs = model(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze()
for i in range(len(labels)): # 遍歷 batch 中的每個樣本
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1
for i in range(10):
if class_total[i] > 0:
print(f'Accuracy of {classes[i]:5s} : {100 * class_correct[i] / class_total[i]:.2f} %')
else:
print(f'Accuracy of {classes[i]:5s} : N/A (no examples)')
這個簡單的 CNN 模型在 CIFAR-10 上的表現可能不會非常頂尖,但它展示了使用 PyTorch 構建、訓練和評估 CNN 的完整流程。要獲得更好的性能,可以嘗試更深的網絡、更複雜的架構 (如 ResNet)、數據增強、更長的訓練時間以及超參數調整。
4.7 (可選) 模型保存與加載
訓練好的模型可以保存下來,以便將來使用或部署。
# 保存模型
PATH = './cifar_cnn_model.pth'
torch.save(model.state_dict(), PATH) # 只保存模型參數
# 加載模型
# 首先,需要重新創建模型實例
loaded_model = SimpleCNN().to(device)
# 然後加載保存的狀態字典
loaded_model.load_state_dict(torch.load(PATH))
loaded_model.eval() # 確保設置為評估模式
# 可以用加載的模型進行預測
# ... (類似於評估部分的代碼)
💡 5. 訓練 CNN 的技巧與注意事項
要訓練出高性能的 CNN 模型,除了設計好網絡架構外,還需要考慮許多訓練技巧:
5.1 數據增強 (Data Augmentation)
- 重要性:深度學習模型通常需要大量的數據才能達到好的泛化能力。數據增強通過對現有訓練數據進行隨機變換 (如翻轉、旋轉、裁剪、顏色抖動等) 來人工擴展訓練集,有助於提高模型的魯棒性 (robustness) 並減少過度擬合。
- PyTorch 實現:
torchvision.transforms
模塊提供了多種數據增強方法。python# 示例:更豐富的數據增強 transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), # 隨機裁剪 transforms.RandomHorizontalFlip(), # 水平翻轉 transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1), # 顏色抖動 transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) # trainset = torchvision.datasets.CIFAR10(..., transform=transform_train)
5.2 學習率調度 (Learning Rate Scheduling)
- 為什麼需要:在訓練初期,較大的學習率可以幫助模型快速收斂;而在訓練後期,較小的學習率有助於模型在最優解附近進行更精細的調整,避免震盪。
- 常見策略:
- StepLR:每隔一定的 epoch 數,將學習率乘以一個因子。 (
torch.optim.lr_scheduler.StepLR
) - MultiStepLR:在指定的 epoch 數時,將學習率乘以一個因子。 (
torch.optim.lr_scheduler.MultiStepLR
) - ReduceLROnPlateau:當某個監控指標 (如驗證集損失) 不再改善時,降低學習率。 (
torch.optim.lr_scheduler.ReduceLROnPlateau
) - CosineAnnealingLR:學習率按照餘弦函數的形狀進行退火。 (
torch.optim.lr_scheduler.CosineAnnealingLR
)
- StepLR:每隔一定的 epoch 數,將學習率乘以一個因子。 (
5.3 正則化 (Regularization)
除了 Dropout 和 Batch Normalization (在一定程度上),其他正則化方法也常用於防止過度擬合:
- L1/L2 正則化 (Weight Decay):在損失函數中加入權重的 L1 或 L2 範數懲罰項。L2 正則化 (Weight Decay) 更常用,可以通過在優化器中設置
weight_decay
參數來實現 (e.g.,optim.Adam(..., weight_decay=1e-4)
)。
5.4 遷移學習 (Transfer Learning)
- 概念:利用在大型數據集 (如 ImageNet) 上預訓練好的模型作為基礎,然後在自己的目標任務和小數據集上進行微調 (fine-tuning)。
- 優勢:
- 當目標任務的數據量較少時,可以顯著提升性能。
- 加快訓練收斂速度。
- 預訓練模型已經學習到了通用的圖像特徵。
- PyTorch 實現:
torchvision.models
提供了許多預訓練模型 (如 ResNet, VGG, AlexNet 等)。pythonimport torchvision.models as models # 加載預訓練的 ResNet18 resnet18_pretrained = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) # 凍結所有參數 (如果只想用作特徵提取器) # for param in resnet18_pretrained.parameters(): # param.requires_grad = False # 替換最後的全連接層以適應新的任務 (例如 CIFAR-10 有 10 個類別) num_ftrs = resnet18_pretrained.fc.in_features resnet18_pretrained.fc = nn.Linear(num_ftrs, 10) # 替換為新的分類頭 resnet18_pretrained = resnet18_pretrained.to(device) # 然後可以像訓練普通模型一樣訓練這個修改後的模型 # 通常,對於新添加的層,使用較大的學習率,對於預訓練部分,使用較小的學習率。
5.5 超參數調整 (Hyperparameter Tuning)
模型的性能對超參數 (如學習率、batch size、網絡層數、濾波器數量、優化器選擇等) 非常敏感。找到最佳的超參數組合通常需要大量的實驗。可以使用網格搜索 (grid search)、隨機搜索 (random search) 或更高級的貝葉斯優化 (Bayesian optimization) 等方法。
5.6 GPU 加速
訓練深度學習模型通常計算量很大。使用 GPU 可以大幅縮短訓練時間。在 PyTorch 中,通過 .to(device)
方法可以將模型和數據張量移動到 GPU 上。確保你的環境配置正確,並且 PyTorch 可以檢測到 GPU。
📝 6. 總結
卷積神經網絡 (CNN) 是深度學習工具箱中不可或缺的一部分,尤其在處理圖像相關任務時威力巨大。本文從 CNN 的基本原理、核心組件 (卷積層、池化層、全連接層) 到經典架構 (LeNet, AlexNet, VGG, GoogLeNet, ResNet) 進行了介紹。我們還通過一個 PyTorch 實戰範例,演示了如何搭建、訓練和評估一個用於圖像分類的 CNN 模型。最後,討論了訓練 CNN 的實用技巧。
PyTorch 提供了靈活而強大的工具來實現和探索 CNN。希望本文能為你打下堅實的基礎,並激發你進一步學習和應用 CNN 的興趣。深度學習是一個快速發展的領域,不斷實踐和探索是提升技能的關鍵!