🔍 開始你的圖像分類之旅:一步一步學習 CIFAR-10 分類
圖像分類是計算機視覺中最基礎的任務之一,如果你是初學者,那么以 CIFAR-10 為訓練場是一個不錯的選擇。本文一步一步帶你從零開始,學習如何用深度學習模型實現圖像分類。
一、CIFAR-10 數據集是什么?
CIFAR-10 是一個小型圖像分類數據集,共包括 10 個類別:? 飛機(airplane)🚗 汽車(automobile)🐦 鳥(bird)🐱 貓(cat)🦌 鹿(deer)🐶 狗(dog)🐸 青蛙(frog)🐴 馬(horse)🚢 船(ship)🚚 卡車(truck)
每張圖片都是 32x32 的小圖,有 RGB 三個顏色通道。
總共有 60000 張圖,其中:
- 訓練集: 50000 張
- 測試集: 10000 張
這些圖片內容豐富,分辨率低,適合初學者練手。
二、模型訓練的整體流程
我們用以下流程完成圖像分類:
- 數據加載和預處理
- 構建模型(CNN)
- 設置損失函數和優化器
- 訓練模型(前向 + 反向傳播 + 更新參數)
- 測試模型效果
🧠 類比理解: 把整個過程比作“學會識別水果”:
- 數據加載:收集不同水果的照片
- 模型:像是大腦處理這些圖像的神經元網絡
- 損失函數:告訴我們判斷錯誤的嚴重程度
- 優化器:幫助我們不斷修正錯誤,直到準確
三、數據加載和預處理
我們使用 PyTorch 中的 transforms 來將圖片:
- 轉換成 Tensor(張量)
- 正則化顏色值到 -1~1 之間,加快模型收斂
import torch
import torchvision
import torchvision.transforms as transforms# 定義圖像轉換操作:將圖片轉換為 Tensor,并進行標準化
transform = transforms.Compose([transforms.ToTensor(), # 將圖片轉為 Tensor 類型,方便計算transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 將圖片標準化,均值和標準差都設為0.5
])# 加載 CIFAR-10 訓練數據集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,download=True, transform=transform)
# 使用 DataLoader 進行批量加載數據,batch_size 是每次加載的圖片數量
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,shuffle=True) # shuffle=True 表示打亂數據# 加載 CIFAR-10 測試數據集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,shuffle=False) # 測試集不需要打亂數據
🎓 例子解釋:
- 如果 batch_size=64,就意味著每次訓練取 64 張圖片
- shuffle=True 可以打亂圖片順序,防止模型記住順序而不是學習特征
🍎 通俗類比:
- ToTensor 就像是把一張照片轉成表格(數字表示顏色)
- Normalize 就像是統一標準,把所有顏色亮度調成統一區間,好比較
四、構建 CNN 模型
CNN(卷積神經網絡)特別適合處理圖像。我們構建一個簡單 CNN 模型,包含:
- 兩個卷積層 + ReLU 激活
- 兩個最大池化層(縮小圖片尺寸)
- 一個全連接隱藏層 + 一個輸出層(10類)
import torch.nn as nn
import torch.nn.functional as F# 定義簡單的卷積神經網絡
class SimpleCNN(nn.Module):def __init__(self):super().__init__()# 第一層卷積層,輸入通道為3(RGB圖像),輸出通道為32,卷積核大小為3x3,padding=1 保證輸出尺寸不變self.conv1 = nn.Conv2d(3, 32, 3, padding=1)# 最大池化層:2x2池化,用來減少圖像尺寸self.pool = nn.MaxPool2d(2, 2)# 第二層卷積層,輸入通道為32,輸出通道為64self.conv2 = nn.Conv2d(32, 64, 3, padding=1)# 全連接層:將卷積層輸出展平為一維向量,連接到一個128維的隱藏層self.fc1 = nn.Linear(64 * 8 * 8, 128) # CIFAR-10圖像尺寸32x32,經過兩次池化后尺寸為8x8# 輸出層:10類,CIFAR-10數據集包含10個類別self.fc2 = nn.Linear(128, 10)def forward(self, x):# 第一層卷積 + ReLU 激活 + 池化x = self.pool(F.relu(self.conv1(x)))# 第二層卷積 + ReLU 激活 + 池化x = self.pool(F.relu(self.conv2(x)))# 展平特征圖為一維向量,便于輸入全連接層x = x.view(-1, 64 * 8 * 8)# 全連接層 + ReLU 激活x = F.relu(self.fc1(x))# 輸出層,返回每個類別的預測概率x = self.fc2(x)return x
📷 類比:
- 卷積操作就像“掃描照片”的濾鏡,用來提取邊緣、顏色塊等圖像特征
- 最大池化像是“縮略圖”,保留最顯著的特征,減少計算量
五、設置損失函數和優化器
import torch.optim as optim# 初始化模型并將其放到 GPU 或 CPU 上
model = SimpleCNN()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)# 損失函數:交叉熵損失函數,用于多分類問題
criterion = nn.CrossEntropyLoss()# 優化器:Adam 優化器,適用于大部分情況,學習率設置為0.001
optimizer = optim.Adam(model.parameters(), lr=0.001)
🧠 例子類比:
- 損失函數就像考試成績:越高說明你錯越多
- 優化器就像“老師指出你的錯誤”并教你怎么改正
📘 數值示例:
- 如果模型預測飛機的概率是 [0.1, 0.05, …, 0.7](第10類)
- 但真實標簽是第1類(飛機),交叉熵損失會很大
- 優化器就會調整參數,使下一次飛機的概率盡量靠近第1類
六、訓練模型:前向、反向、更新
# 訓練過程:迭代多個 epoch,每個 epoch 會遍歷所有訓練數據
for epoch in range(10): # 總共訓練 10 個 epochrunning_loss = 0.0 # 每個 epoch 初始化損失值為 0for inputs, labels in trainloader: # 遍歷每個 batchinputs, labels = inputs.to(device), labels.to(device) # 將數據移到 GPU 或 CPUoptimizer.zero_grad() # 清除上一次的梯度outputs = model(inputs) # 前向傳播,得到每張圖片的預測結果loss = criterion(outputs, labels) # 計算損失值loss.backward() # 反向傳播,計算梯度optimizer.step() # 更新模型參數running_loss += loss.item() # 累加損失值print(f"Epoch {epoch + 1}, Loss: {running_loss:.3f}") # 打印當前 epoch 的損失
📐 具體數值例子:
- 模型初始權重是 0.3,預測錯誤 → loss = 2.5
- 反向傳播算出權重梯度是 -0.8
- 學習率為 0.01,更新后權重 = 0.3 - 0.01 × (-0.8) = 0.308
七、測試模型效果
correct = 0 # 初始化正確預測的個數
total = 0 # 初始化總預測的個數
model.eval() # 設置模型為評估模式,關閉 Dropout 等訓練時的特殊操作with torch.no_grad(): # 在測試時,不需要計算梯度,減少計算量for data in testloader: # 遍歷測試集中的數據images, labels = dataimages, labels = images.to(device), labels.to(device) # 將數據移到 GPU 或 CPUoutputs = model(images) # 獲取模型的輸出_, predicted = torch.max(outputs, 1) # 獲取預測結果,torch.max 返回最大值和其索引,這里我們只取索引total += labels.size(0) # 累加總的測試樣本數量correct += (predicted == labels).sum().item() # 統計預測正確的樣本數量print(f"測試準確率: {100 * correct / total:.2f}%") # 打印測試集上的準確率
📊 例子:
- 如果 total=10000,correct=7000,準確率就是 70%
🏁 完整代碼快速運行包
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import matplotlib.pyplot as plt # 添加matplotlib用于可視化
from matplotlib import rcParams # 用于設置字體# 1. 數據加載與預處理
transform = transforms.Compose([transforms.ToTensor(), # 將圖像轉換為Tensortransforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 歸一化到[-1, 1]之間
])# 加載訓練集與測試集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False)# 2. 定義模型:簡單的CNN模型
class SimpleCNN(nn.Module):def __init__(self):super().__init__()# 第一個卷積層:3個輸入通道,32個輸出通道,卷積核大小3x3,padding為1self.conv1 = nn.Conv2d(3, 32, 3, padding=1)# 最大池化層:2x2池化self.pool = nn.MaxPool2d(2, 2)# 第二個卷積層:32個輸入通道,64個輸出通道,卷積核大小3x3,padding為1self.conv2 = nn.Conv2d(32, 64, 3, padding=1)# 全連接層,輸入大小64x8x8,輸出128self.fc1 = nn.Linear(64 * 8 * 8, 128)# 最后一層全連接層,輸出10個類別self.fc2 = nn.Linear(128, 10)def forward(self, x):x = self.pool(F.relu(self.conv1(x))) # 卷積+ReLU+池化x = self.pool(F.relu(self.conv2(x))) # 卷積+ReLU+池化x = x.view(-1, 64 * 8 * 8) # 展平數據,準備全連接x = F.relu(self.fc1(x)) # 全連接+ReLUx = self.fc2(x) # 最后一層輸出return x# 3. 初始化模型、損失函數與優化器
model = SimpleCNN()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 判斷是否使用GPU
model.to(device) # 將模型轉移到GPU或CPU上criterion = nn.CrossEntropyLoss() # 使用交叉熵作為損失函數
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam優化器,學習率為0.001# 4. 訓練模型
for epoch in range(10): # 訓練10個epochrunning_loss = 0.0for inputs, labels in trainloader:inputs, labels = inputs.to(device), labels.to(device) # 數據轉移到GPU或CPUoptimizer.zero_grad() # 清除上一次的梯度outputs = model(inputs) # 前向傳播loss = criterion(outputs, labels) # 計算損失loss.backward() # 反向傳播optimizer.step() # 優化器更新參數running_loss += loss.item() # 累加損失print(f"Epoch {epoch + 1}, Loss: {running_loss:.3f}") # 打印每個epoch的損失# 5. 測試模型效果
correct = 0
total = 0
model.eval() # 將模型設置為評估模式with torch.no_grad(): # 禁止計算梯度,提高效率for data in testloader:images, labels = dataimages, labels = images.to(device), labels.to(device) # 數據轉移到GPU或CPUoutputs = model(images)_, predicted = torch.max(outputs, 1) # 獲取最大概率的類別total += labels.size(0) # 累加總樣本數correct += (predicted == labels).sum().item() # 統計正確的樣本數# 可視化一個批次的預測結果plt.figure(figsize=(10, 10))for i in range(8): # 顯示前8張圖片plt.subplot(2, 4, i + 1)plt.imshow(images[i].cpu().permute(1, 2, 0) * 0.5 + 0.5) # 反歸一化并顯示圖片plt.title(f"Label: {labels[i].item()}\nPrediction: {predicted[i].item()}")plt.axis('off')plt.show()break # 僅顯示一個批次# 輸出測試準確率
print(f"測試準確率: {100 * correct / total:.2f}%")
你可以將以下內容保存為 train_cifar10.py
并運行:
python train_cifar10.py
💡 不需要修改任何內容就能開始訓練和測試!有 CUDA 就用 GPU,否則自動使用 CPU。