【深度學習CV】【圖像分類】從CNN(卷積神經網絡)、ResNet遷移學習到GPU高效訓練優化【案例代碼】詳解

摘要

本文分類使用的是resNet34,什么不用yolo v8,yolo v10系列,雖然他們也可以分類,因為yolo系列模型不純粹,里面包含了目標檢測的架構,所以分類使用的是resNet

本文詳細介紹了三種不同的方法來訓練卷積神經網絡進行 CIFAR-10 圖像分類任務,幫助讀者從零開始學習如何設計和優化深度學習模型。首先,我們通過手工設計一個卷積神經網絡并進行訓練,講解每個層次的作用和設計思想;接著,我們使用一個開源模型 ResNet34,并對其進行微調以適應我們的數據集;最后,我們通過一系列高效的訓練技巧,顯著提高訓練效率,減少訓練時間。最終,您將不僅能實現這一目標,而且能夠舉一反三,設計自己的模型,提升模型效率。每個批次由原來的111秒,變成12秒,準確率最終達到了90.24 %。


引言

在深度學習的世界中,圖像分類任務無疑是最經典且最具挑戰性的任務之一。CIFAR-10 數據集作為經典的圖像分類數據集,包含了10個類別的32x32像素彩色圖像,廣泛用于模型訓練和測試。今天,我們將介紹如何使用三種不同的方法來訓練卷積神經網絡(CNN)以完成 CIFAR-10 的分類任務。

本文的目標不僅是教會你如何實現這些方法,更重要的是讓你理解背后的設計思想,以及如何將這些思想轉化為你自己的模型。通過閱讀本文,你將掌握設計卷積神經網絡、使用開源模型以及進行高效訓練的能力。

目錄

  • 第一部分:手工設計卷積神經網絡
    • 1.1 網絡結構設計思路
    • 1.2 訓練與驗證模型
    • 1.3 訓練效果與分析
  • 第二部分:使用開源模型——ResNet34
    • 2.1 為什么選擇 ResNet34
    • 2.2 如何微調 ResNet34 適應 CIFAR-10
    • 2.3 訓練與驗證模型
    • 2.4 訓練效果與分析
  • 第三部分:高效訓練技巧
    • 3.1 為什么需要優化訓練效率
    • 3.2 高效訓練技巧與代碼優化
    • 3.3 訓練效果與分析
  • 總結與展望

第一部分:手工設計卷積神經網絡

在本部分,我們將手工設計一個卷積神經網絡,用于進行 CIFAR-10 圖像分類。我們將會從設計網絡結構開始,逐步增加網絡層,并講解每一層的作用。

1.1 網絡結構設計思路

卷積神經網絡(CNN)通常由以下幾部分組成:

  1. 卷積層:用于提取圖像的特征。通過卷積核(或濾波器)對輸入圖像進行卷積運算,得到特征圖。
  2. 激活層:使用非線性激活函數,如 ReLU,來增加網絡的表達能力。
  3. 池化層:對卷積后的特征圖進行下采樣,減小數據維度,保留重要特征。
  4. 全連接層:將卷積和池化操作提取的特征映射到最終的分類輸出。
  5. Dropout:用于防止過擬合。

根據這些部分,我們設計了一個簡單的卷積神經網絡,包含兩層卷積層、兩層全連接層、以及 Dropout。

1.2 訓練與驗證模型

使用 PyTorch 框架,我們構建了卷積神經網絡,并加載了 CIFAR-10 數據集。下面是訓練過程的代碼:(完整代碼在最后面,此處只展示相關代碼)

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor, Compose
import timeclass ImageClassification(nn.Module):def __init__(self):super(ImageClassification, self).__init__()# --------------------# 卷積層1:輸入通道=3(RGB),輸出通道=6,卷積核=3x3# 主要用來提取圖像初步特征# --------------------self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3)# --------------------# BatchNorm1:對6個通道進行批歸一化# 可以加速收斂、穩定訓練# --------------------self.bn1 = nn.BatchNorm2d(num_features=6)# --------------------# MaxPool層:核大小=2x2,步幅=2# 進行下采樣,減小特征圖空間尺寸# --------------------self.pool = nn.MaxPool2d(kernel_size=2, stride=2)# --------------------# 卷積層2:輸入通道=6,輸出通道=16,卷積核=3x3# 繼續提取更深層次特征# --------------------self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=3)# --------------------# BatchNorm2:對16個通道進行批歸一化# --------------------self.bn2 = nn.BatchNorm2d(num_features=16)# --------------------# 全連接層1:輸入576(16通道 * 6 * 6) => 輸出120# 提取卷積層輸出的全局特征# --------------------self.fc1 = nn.Linear(in_features=576, out_features=120)# --------------------# 全連接層2:輸入120 => 輸出84# 進一步映射到更低維空間# --------------------self.fc2 = nn.Linear(in_features=120, out_features=84)# --------------------# 全連接層3:輸入84 => 輸出10# 最終分類層,對應CIFAR10的10個類別# --------------------self.fc3 = nn.Linear(in_features=84, out_features=10)# --------------------# Dropout:在全連接層后,隨機失活一些神經元# 防止過擬合# --------------------self.dropout = nn.Dropout(p=0.5)def forward(self, x):# x shape: (batch_size, 3, 32, 32)# 第一次卷積 => 批歸一化 => ReLU => MaxPool# 結果 shape: (batch_size, 6, 14, 14)x = self.pool(torch.relu(self.bn1(self.conv1(x))))# 第二次卷積 => 批歸一化 => ReLU => MaxPool# 結果 shape: (batch_size, 16, 6, 6)x = self.pool(torch.relu(self.bn2(self.conv2(x))))# 展平 => (batch_size, 16*6*6=576)x = x.view(-1, 576)# 全連接層1 => ReLU => Dropoutx = torch.relu(self.fc1(x))x = self.dropout(x)# 全連接層2 => ReLU => Dropoutx = torch.relu(self.fc2(x))x = self.dropout(x)# 最后全連接層 => 輸出10維(對應CIFAR10的10個類別)x = self.fc3(x)return x

主要流程:

1. 兩次卷積 + 批歸一化 + ReLU + 池化

提取特征并減小特征圖空間尺寸。

2. Flatten

將卷積層輸出展平為一維向量,方便進入全連接層。

3. 全連接層 + Dropout

將特征映射到分類空間。Dropout 隨機失活部分神經元,減少過擬合。

4. 輸出 10 類

對應 CIFAR-10 的 10 個類別(0~9)。

1.3 訓練效果與分析

經過100個epoch的訓練,我們的網絡達到了約64.85%的準確率。盡管這是一個不錯的起點,但顯然可以進一步提高網絡的表現。因此,我們決定使用開源模型 ResNet34 來嘗試提升準確率。


第二部分:使用開源模型——ResNet34

在這一部分,我們將展示如何使用 ResNet34 這一經過驗證的模型,并通過微調使其適應我們的 CIFAR-10 數據集。

2.1 為什么選擇 ResNet34

ResNet34 是一個深度神經網絡,由多個殘差模塊(Residual Blocks)組成。與傳統的 CNN 相比,ResNet34 能夠通過跳躍連接(skip connections)解決深度網絡中的梯度消失問題,從而使模型能夠在非常深的層數中仍然保持有效的學習能力。

2.2 如何微調 ResNet34 適應 CIFAR-10

ResNet34 在默認情況下是為 ImageNet 數據集設計的,因此我們需要對其進行一些修改,以適應 CIFAR-10 數據集。具體來說,我們將調整第一層卷積層和最后的全連接層。

import torchvision.models as models  # 導入 torchvision 模型模塊,便于加載 ResNet34 模型
import torch.nn as nn             # 導入 torch.nn 模塊,用于構建和修改神經網絡層def get_resnet34_cifar10():# 加載 ResNet34 模型,注意這里不使用預訓練權重(pretrained=False)# 因為我們可能希望在 CIFAR-10 數據集上從頭微調模型,或者你想使用自己的預訓練權重model = models.resnet34(pretrained=False)# 修改第一層卷積層(conv1):# 原始 ResNet34 中的 conv1 是一個 7x7 卷積,stride=2,這會使 32x32 的 CIFAR-10 圖像尺寸迅速減小# 因此我們將其替換為 3x3 卷積,stride=1,padding=1,以保持圖像尺寸更穩定,便于后續的特征提取model.conv1 = nn.Conv2d(in_channels=3,       # 輸入通道數為3(RGB圖像)out_channels=64,     # 輸出通道數為64,與原始網絡保持一致kernel_size=3,       # 使用 3x3 卷積核stride=1,            # 步幅設為1,不進行下采樣padding=1,           # 填充1,保證卷積后輸出尺寸不變bias=False           # 不使用偏置項(后續的 BatchNorm 會處理偏置問題))# 修改最后一層全連接層(fc):# 原始 ResNet34 的 fc 層用于 ImageNet 分類,其輸出類別數為1000# 這里我們將其替換為適用于 CIFAR-10 的全連接層,輸出類別數設置為10model.fc = nn.Linear(in_features=model.fc.in_features,  # 保持原 fc 層輸入特征數不變out_features=10                    # 輸出特征數設為10,匹配 CIFAR-10 的類別數量)# 返回修改后的模型return model

代碼說明

1. 導入模塊

? torchvision.models:用于加載各種預定義的模型架構,如 ResNet34。

? torch.nn:提供構建和修改神經網絡層所需的類和函數。

2. 加載 ResNet34 模型,但沒有使用模型的網絡權重

使用 models.resnet34(pretrained=False) 加載 ResNet34 模型,不使用預訓練權重,因為 CIFAR-10 圖像尺寸較小或你可能希望從頭微調模型。

3. 修改第一層卷積

? 原始 ResNet34 的第一層卷積層使用的是 7x7 卷積核和 stride=2,會使輸入圖像(CIFAR-10 的 32x32 圖像)尺寸大幅度縮小。

? 為了適應 CIFAR-10 的圖像尺寸,我們使用 3x3 卷積核、stride=1 和 padding=1,這樣可以保持特征圖尺寸相對穩定,有利于后續特征提取。

4. 修改全連接層

? 原模型的全連接層輸出 1000 個類別,這對應于 ImageNet 數據集。

? 將其修改為輸出 10 個類別,適應 CIFAR-10 數據集的分類任務。

通過這些修改,這個函數返回一個經過定制化調整后的 ResNet34 模型,適合用來處理 CIFAR-10 圖像分類任務。


2.3 訓練與驗證模型

在微調完成后,我們就可以使用 ResNet34 來進行訓練。訓練過程類似于代碼1中的流程,但我們直接使用開源模型并做出適應性調整。

2.4 訓練效果與分析

使用 ResNet34 后,我們發現訓練時間顯著提高,每個批次需要112秒,這么算100批次就要訓練3小時。雖然準確率有所提升,但我們希望通過進一步優化訓練過程來提高訓練效率。因此直接手動kill? 掉了訓練過程,下面是我們的優化過程


第三部分:高效訓練技巧

現在,我們已經有了一個效果較好的模型(ResNet34)。但是,訓練一個大型網絡,如 ResNet34,可能需要很長時間。為了提高訓練效率,我們在這一部分介紹了幾種高效訓練的技巧。

3.1 為什么需要優化訓練效率

在使用 GPU 訓練時,盡管我們的 GPU(V100)具有強大的計算能力,但如果訓練過程中的數據加載、內存傳輸等環節沒有得到優化,GPU 的計算能力將得不到充分發揮。

3.2 高效訓練技巧與代碼優化

為了優化訓練,我們采用了以下幾種方法:

  1. 增加 num_workerspin_memory:通過多線程加速數據加載,同時使用 pin_memory 將數據從主機內存直接傳輸到 GPU,減少數據傳輸的時間。
  2. 混合精度訓練:通過 torch.cuda.amp 進行混合精度訓練,這可以在不損失精度的情況下提高計算效率。
  3. 調整批量大小(Batch Size):通過增加批量大小來提高 GPU 的利用率。
# 創建訓練集和驗證集的 DataLoader
# DataLoader 用于按批次加載數據,加快訓練過程并支持多線程數據預處理
train_loader = DataLoader(train_dataset,           # 訓練數據集batch_size=128,          # 每個批次加載128張圖像shuffle=True,            # 每個epoch開始前隨機打亂數據,增強模型泛化能力num_workers=8,           # 使用8個子進程并行加載數據(可根據CPU核心數調整)pin_memory=True          # 將數據加載到鎖頁內存中,方便快速轉移到GPU
)valid_loader = DataLoader(valid_dataset,           # 驗證數據集batch_size=128,          # 每個批次加載128張圖像shuffle=False,           # 驗證時不需要打亂數據,保持順序便于評估num_workers=8,           # 同樣使用8個子進程加速加載pin_memory=True          # 使用鎖頁內存,提高數據傳輸速度
)# 初始化混合精度訓練的梯度縮放器
# 混合精度訓練(Automatic Mixed Precision, AMP)可以使用float16加速訓練并降低顯存占用
scaler = torch.cuda.amp.GradScaler()# 開始訓練過程,遍歷指定的訓練輪數(EPOCHS)
for epoch in range(EPOCHS):model.train()  # 切換模型到訓練模式,啟用Dropout和BatchNorm的訓練行為for x, y in train_loader:# 將優化器中的梯度清零,防止梯度累加optimizer.zero_grad()# 自動混合精度上下文,啟用 float16 運算以提高效率with torch.cuda.amp.autocast(device_type=device.type):output = model(x)           # 前向傳播:計算模型輸出loss = criterion(output, y) # 計算損失:比較模型輸出和真實標簽# 通過梯度縮放器將損失值進行縮放后反向傳播,防止梯度溢出scaler.scale(loss).backward()# 使用梯度縮放器更新參數(結合自動混合精度運算的結果)scaler.step(optimizer)# 更新梯度縮放器狀態,為下一個迭代做準備scaler.update()

詳細說明

1. DataLoader 配置

? batch_size=128:每個批次包含128張圖片,有助于充分利用GPU的并行計算能力。

? shuffle=True (訓練集):在每個epoch開始前打亂數據,防止模型記住數據順序,提高泛化能力。驗證集通常不打亂。

? num_workers=8:開啟8個工作進程來加載數據,加快數據預處理和加載速度。

? pin_memory=True:將加載的數據存放在頁鎖定內存中,以便更快地傳輸到GPU中。

2. 混合精度訓練(AMP)

? torch.cuda.amp.GradScaler():初始化梯度縮放器,用于在混合精度訓練時動態調整梯度縮放因子,防止數值不穩定。

? with torch.cuda.amp.autocast():在此上下文中,部分計算會自動采用半精度(float16)運算,既加快了計算速度,又降低顯存消耗。

3. 訓練循環

? optimizer.zero_grad():清零梯度,防止前一次梯度累加。

? 模型前向傳播:將輸入數據傳入模型,得到輸出。

? 計算損失:使用損失函數(如交叉熵損失)比較模型輸出與真實標簽。

? 梯度反向傳播和更新:通過梯度縮放器縮放損失后反向傳播,再通過 scaler.step(optimizer) 更新模型參數,最后更新 scaler 的狀態以適應下一次計算。

這樣設置可以充分利用混合精度訓練的優勢,提高訓練速度并降低顯存占用,同時保證模型的數值穩定性。

3.3 訓練效果與分析

經過這些優化,我們的訓練時間從每個批次 111 秒縮短至 12 秒,由原來的3小時,變成了20分鐘,顯著提高了訓練效率。從而且,準確率保持在了 87.5%。


第四部分:使用預訓練權重

4.1 為什么需要ImageNet的原始權重

對于 CIFAR-10 這種數據集(相對較小且與 ImageNet 有一定相似性),大多數情況下建議使用預訓練權重。使用預訓練權重的優勢主要有:

? 加速收斂:預訓練模型已經學到了一些通用的低級特征(如邊緣、紋理等),在微調時可以更快達到較好的效果。

? 更高的準確率:特別是在數據量不足以從頭訓練深層網絡時,預訓練權重可以幫助避免過擬合,并提高最終的分類準確率。

? 穩定性更好:預訓練模型在大規模數據集(如 ImageNet)上經過充分訓練,其特征具有較好的泛化性,遷移到 CIFAR-10 上通常能獲得較穩定的性能。

當然,如果你有足夠的數據或你希望針對特定領域做更深入的調整,也可以考慮從頭訓練,但這往往需要更多的計算資源和更長的訓練時間。

綜上所述,我的建議是:對于 CIFAR-10 等常見數據集,建議使用原始的 ImageNet 預訓練權重,然后進行微調。這通常能帶來更好的效果和更快的訓練收斂。

4.2?使用ImageNet的原始權重代碼

import torchvision.models as models  # 導入 torchvision 中的預定義模型
import torch.nn as nn               # 導入 torch.nn 模塊,用于構建和修改網絡層def get_resnet34_cifar10():"""構建適用于 CIFAR-10 圖像分類任務的 ResNet34 模型。該函數主要做了以下三處修改:1) 修改第一個卷積層(conv1):將原來的 7x7 卷積替換成 3x3 卷積,并設置 stride=1 和 padding=1,以防止 CIFAR-10 圖像(32×32)的尺寸被過度縮小。2) 修改最大池化層(maxpool):將其替換成 Identity(),也就是不做任何池化操作。3) 修改最后全連接層(fc):將輸出類別數從原來的 1000 改為 10(因為 CIFAR-10 有 10 類)。"""# 使用 torchvision 官方提供的 ResNet34 模型,并加載在 ImageNet 1K 數據集上預訓練的權重# 這里使用的是 weights=models.ResNet34_Weights.IMAGENET1K_V1net = models.resnet34(weights=models.ResNet34_Weights.IMAGENET1K_V1)# weights=models.ResNet34_Weights.IMAGENET1K_V1 的解釋:# - models.ResNet34_Weights 是一個枚舉類,其中定義了 ResNet34 模型可用的預訓練權重選項。# - IMAGENET1K_V1 是該枚舉中的一個具體成員,表示在 ImageNet 1K 數據集上訓練得到的第一個版本的預訓練權重。#   使用這個權重可以讓模型初始狀態已經學到大量通用的視覺特征,#   這對于遷移學習和微調任務(例如 CIFAR-10 分類)非常有幫助。# 修改第一層卷積層:# 原始 ResNet34 的 conv1 層默認使用的是 7x7 卷積核,stride=2,# 這對于尺寸只有32x32的 CIFAR-10 圖像來說會過于激進,導致特征圖尺寸急劇減小,# 所以這里我們使用一個 3x3 的卷積核,并設置 stride=1 和 padding=1,# 這樣可以更好地保留圖像的細節信息。net.conv1 = nn.Conv2d(in_channels=3,    # 輸入通道數為3(RGB圖像)out_channels=64,  # 輸出通道數為64,保持與原模型一致kernel_size=3,    # 使用 3x3 的卷積核stride=1,         # 步幅設為1,不進行下采樣padding=1,        # 填充1,保證卷積后輸出尺寸與輸入相同bias=False        # 不使用偏置項(通常和 BatchNorm 搭配時可省略偏置))# 修改 maxpool 層:# 原始模型中 conv1 后接有一個 3x3 的最大池化層(stride=2),# 用于快速減小特征圖尺寸,但對于小尺寸圖像(如32x32)來說可能導致信息丟失,# 因此這里將 maxpool 替換為 Identity() 層,即直接將輸入原樣傳遞,不做池化操作。net.maxpool = nn.Identity()# 修改全連接層:# 原始 ResNet34 的 fc 層輸出 1000 維向量(對應 ImageNet 1000 個類別),# 這里我們將其替換為一個新的全連接層,使得輸出類別數變為 10,# 以適應 CIFAR-10 數據集的分類任務。net.fc = nn.Linear(in_features=net.fc.in_features,  # 保持輸入特征數不變(通常為512)out_features=10,                 # 輸出10個類別的概率bias=True                        # 使用偏置項)# 返回經過修改的模型return net

詳細講解

1. 預訓練權重的選擇:

? 使用 weights=models.ResNet34_Weights.IMAGENET1K_V1 可以加載在 ImageNet 1K 數據集上預訓練好的權重。

? 為什么選擇這個?

? 預訓練優勢:ImageNet 數據集包含百萬級圖像和1000個類別,模型在此數據上學到了大量通用的視覺特征(如邊緣、紋理等),這些特征在其他圖像分類任務中(例如 CIFAR-10)也非常有效。

? 加速收斂:預訓練模型提供了一個較好的初始化狀態,微調時可以更快達到較優性能,避免從頭訓練帶來的長時間收斂問題。

? 穩定性:預訓練權重通常經過大量數據訓練,具有良好的泛化能力,在數據量較少的 CIFAR-10 上使用可以減少過擬合風險。

2. 為什么修改第一層卷積層:

? 原始 ResNet34 的第一層是 7x7 卷積,stride=2,這對 ImageNet(224×224)這樣的高分辨率圖像效果很好,但對于 32×32 的 CIFAR-10 圖像來說,會使得特征圖尺寸過小,從而丟失很多細節信息。

? 將卷積核修改為 3x3、stride=1 和 padding=1 后,可以保持較多原始信息,有助于在后續層中提取更有區分度的特征。

3. 為什么去掉 MaxPool 層:

? 在原始 ResNet 中,conv1 后的池化層用于進一步減小特征圖尺寸。但對于 CIFAR-10 這種低分辨率圖像,過早下采樣會使得信息量嚴重減少。

? 用 nn.Identity() 替換 maxpool 層,即讓數據原樣傳遞,不做任何池化,這樣可以保留更多細節,有助于分類任務。

4. 修改全連接層的原因:

? 原始 ResNet34 的全連接層輸出 1000 個類別,用于 ImageNet 分類。

? CIFAR-10 數據集只有 10 個類別,所以需要將 fc 層的輸出維度調整為 10,以適應具體任務。


4.3 訓練效果與分析

?經過使用原始權重這些優化,我們的訓練時間依舊 12 秒,但是顯著提高了準確率。準確率保持在了 90.24 %。

因此90.24 %準確率的分類,該分類模型已經完成了產品的需求是可以直接應用市場的了~


?4.3?關于應用自己訓練或他人訓練的模型權重

使用自己訓練的模型權重

? 假設你已經使用類似的網絡結構訓練了一個模型,并將權重保存到了一個文件(例如 my_weights.pth),你可以這樣加載并應用權重:

# 加載自己訓練好的權重文件
state_dict = torch.load('my_weights.pth', map_location=device)
model = get_resnet34_cifar10()  # 構建模型架構
model.load_state_dict(state_dict)  # 將保存的權重加載到模型中

? 這里需要注意:如果你保存的是整個模型(包括結構和權重),使用 torch.load 直接加載可能需要調用 torch.load('my_model.pth');但通常建議只保存權重(state_dict),這樣更靈活。

使用他人訓練的模型權重也是一樣的

? 如果你下載了他人訓練好的權重文件,流程與上面類似:

state_dict = torch.load('downloaded_weights.pth', map_location=device)
model = get_resnet34_cifar10()  # 構建模型架構
model.load_state_dict(state_dict)  # 加載他人訓練好的權重

? 確保你下載的權重文件與模型結構完全一致,否則可能出現加載錯誤。如果權重來自不同的版本或結構,需要先做適配。

通過這種方式,無論是使用自己訓練的權重還是下載的權重,你都可以將預訓練好的參數加載到模型中,進行微調或直接推理。


完整代碼全過程:


代碼1:手工設計卷積神經網絡分類模型
?

import torch
import torch.nn as nn
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor, Compose
import torch.optim as optim
from torch.utils.data import DataLoader
import time
import matplotlib.pyplot as plt
from torchsummary import summary# 配置字體參數
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False# 額外導入,用于混淆矩陣可視化
import numpy as np
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay# 全局超參數
BATCH_SIZE = 8           # 批量大小
EPOCHS = 100              # 訓練輪數(演示時設置10,實際可設置更多)
LEARNING_RATE = 1e-3     # 學習率def create_dataset():"""加載 CIFAR10 數據集,并返回訓練集和測試集。數據集下載路徑: root='data'這里使用 ToTensor() 將圖片轉為 PyTorch 張量。Returns:train (Dataset): 訓練集valid (Dataset): 測試集"""train = CIFAR10(root='data',train=True,transform=Compose([ToTensor()]),download=True)valid = CIFAR10(root='data',train=False,transform=Compose([ToTensor()]),download=True)return train, validclass ImageClassification(nn.Module):"""該卷積神經網絡用于對 CIFAR10 數據集進行分類。輸入: 3 通道圖像 (3, 32, 32)輸出: 10 類別網絡結構(加入 BatchNorm 后):- Conv1: 輸入通道=3, 輸出通道=6, 卷積核=3x3- BatchNorm1: 對6個通道進行歸一化- ReLU- MaxPool1: 核大小=2x2, 步長=2- Conv2: 輸入通道=6, 輸出通道=16, 卷積核=3x3- BatchNorm2: 對16個通道進行歸一化- ReLU- MaxPool2: 核大小=2x2, 步長=2- Flatten => 全連接層- Linear1: 輸入576 -> 輸出120- ReLU- Dropout (防止過擬合)- Linear2: 輸入120 -> 輸出84- ReLU- Dropout- Output: 輸入84 -> 輸出10"""def __init__(self):super(ImageClassification, self).__init__()# 第一個卷積層self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1)# 第一個批歸一化層self.bn1 = nn.BatchNorm2d(num_features=6)# 第一個池化層self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)# 第二個卷積層self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=3, stride=1)# 第二個批歸一化層self.bn2 = nn.BatchNorm2d(num_features=16)# 第二個池化層self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)# 全連接層前,特征圖大小 = 16通道 * 6 * 6 = 576self.linear1 = nn.Linear(in_features=576, out_features=120)self.dropout1 = nn.Dropout(p=0.5)  # 添加 Dropoutself.linear2 = nn.Linear(in_features=120, out_features=84)self.dropout2 = nn.Dropout(p=0.5)  # 添加 Dropoutself.out = nn.Linear(in_features=84, out_features=10)def forward(self, x):"""前向傳播流程:1) Conv1 -> BatchNorm1 -> ReLU -> Pool12) Conv2 -> BatchNorm2 -> ReLU -> Pool23) Flatten4) Linear1 -> ReLU -> Dropout5) Linear2 -> ReLU -> Dropout6) 輸出層"""# 第一次卷積x = self.conv1(x)# 加入 BatchNorm1 歸一化x = self.bn1(x)# 激活函數x = torch.relu(x)# 第一次池化x = self.pool1(x)# 第二次卷積x = self.conv2(x)# 加入 BatchNorm2 歸一化x = self.bn2(x)# 激活函數x = torch.relu(x)# 第二次池化x = self.pool2(x)# 將特征圖展平為向量x = x.view(x.size(0), -1)# 全連接層 + 激活 + Dropoutx = torch.relu(self.linear1(x))x = self.dropout1(x)x = torch.relu(self.linear2(x))x = self.dropout2(x)# 輸出層x = self.out(x)return xdef validate_model(model, valid_loader, criterion, device):"""在驗證集上評估模型的損失和準確率。參數:model (nn.Module): 當前待評估的神經網絡模型valid_loader (DataLoader): 驗證集數據加載器,用于逐批次提供驗證數據criterion: 損失函數,這里通常使用交叉熵損失函數 CrossEntropyLoss返回:avg_loss (float): 驗證集上的平均損失值accuracy (float): 驗證集上的整體準確率"""model.eval()  # 將模型設置為評估模式。在評估模式下,Dropout、BatchNorm等層會表現為推理模式,# 例如 Dropout 會關閉隨機失活,BatchNorm會使用固定的均值和方差進行歸一化,# 這樣可以確保在驗證或測試時模型行為穩定,不受訓練時隨機因素的影響。total_loss = 0.0    # 初始化累計損失,用于累加每個批次計算得到的損失值total_correct = 0   # 初始化累計正確預測的樣本數,用于計算準確率total_samples = 0   # 初始化累計樣本總數,用于后續計算準確率# 使用 torch.no_grad() 上下文管理器,表示在該代碼塊內不計算梯度,降低內存消耗、提高運行速度。with torch.no_grad():# 遍歷驗證集 DataLoader 中的每個批次數據for x, y in valid_loader:x, y = x.to(device), y.to(device)# x: 當前批次輸入數據,形狀通常為 (batch_size, channels, height, width)# y: 當前批次對應的真實標簽,形狀通常為 (batch_size,)output = model(x)  # 將輸入 x 傳入模型進行前向傳播,得到輸出結果 output,# output 通常是一個形狀為 (batch_size, num_classes) 的張量,# 每一行表示每個樣本各類別的預測分數或概率(在未經過 softmax 的情況下為logits)loss = criterion(output, y)# 計算當前批次的損失值:# 將模型輸出 output 與真實標簽 y 輸入到損失函數中,# 例如對于多分類問題常用的 CrossEntropyLoss,該損失函數內部會對 output 進行 softmax 計算,# 并根據真實標簽 y 計算交叉熵損失。total_loss += loss.item()# loss.item() 將當前批次的損失值從張量中取出為 Python 數值,# 并累加到 total_loss 中,目的是統計整個驗證集上的累計損失。preds = torch.argmax(output, dim=-1)# torch.argmax(output, dim=-1) 沿著最后一個維度(類別維度)取最大值的索引,# 得到模型對每個樣本預測的類別標簽。即預測類別為得分最高的那一項。total_correct += (preds == y).sum().item()# (preds == y) 會返回一個布爾型張量,表示每個預測是否與真實標簽相等,# .sum() 對布爾值進行求和(True 計為1,False計為0),得到本批次預測正確的數量,# .item() 將結果轉換為 Python 數值,并累加到 total_correct 中。total_samples += len(y)# 累加本批次樣本數。 len(y) 返回當前批次的樣本數量,# 累加到 total_samples 中,用于后續計算整體準確率。# 計算整個驗證集的平均損失avg_loss = total_loss / len(valid_loader)# len(valid_loader) 表示驗證集中批次數目,所以 avg_loss 是每個批次的平均損失值。# 計算驗證集的準確率accuracy = total_correct / total_samples# 用累計正確預測數除以總樣本數,得到準確率(一個介于0到1之間的浮點數)return avg_loss, accuracydef train_model(model, train_dataset, valid_dataset, device):"""使用給定的模型和訓練數據集進行訓練,并在每個Epoch結束后在驗證集上評估。- 損失函數: CrossEntropyLoss(適用于多分類問題)- 優化器: AdamW(自帶權重衰減,改進版Adam)- 保存最優模型(基于驗證集準確率)- 記錄訓練過程中每個Epoch的Loss和Accuracy,用于后續可視化"""# 創建交叉熵損失函數對象,用于計算模型輸出和真實標簽之間的誤差criterion = nn.CrossEntropyLoss()# 創建AdamW優化器,傳入模型所有參數和設定學習率optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)# 利用 DataLoader 將訓練數據集按照指定批量大小隨機打亂后加載train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)# 驗證集 DataLoader,不需要打亂順序valid_loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False)# 初始化兩個列表,用于保存每個Epoch的訓練損失和驗證損失train_losses, valid_losses = [], []# 初始化兩個列表,用于保存每個Epoch的訓練準確率和驗證準確率train_accuracies, valid_accuracies = [], []# 初始化一個變量記錄歷史最高驗證準確率,用于保存最佳模型best_acc = 0.0# 指定保存最佳模型的文件路徑best_model_path = './深度學習實戰/model/image_classification/image_classification_best.pth'# 開始遍歷每個訓練周期(Epoch)for epoch_idx in range(EPOCHS):# 將模型設置為訓練模式,這會啟用Dropout、BatchNorm等訓練時的特殊行為model.train()# 記錄當前Epoch開始的時間,用于計算訓練耗時start_time = time.time()# 初始化本Epoch內累計的訓練損失、正確預測數和樣本總數total_loss = 0.0total_correct = 0total_samples = 0# 遍歷訓練數據加載器中的每個批次數據for x, y in train_loader:# 將當前批次數據遷移到指定設備x, y = x.to(device), y.to(device)# 前向傳播:將當前批次輸入數據 x 傳入模型,得到預測輸出 outputoutput = model(x)# 計算當前批次的損失值:將模型輸出和真實標簽 y 輸入到損失函數中計算交叉熵損失loss = criterion(output, y)# 優化器梯度歸零,防止梯度累加(PyTorch中梯度默認累加)optimizer.zero_grad()# 反向傳播:根據當前損失計算梯度loss.backward()# 更新模型參數:根據計算得到的梯度和優化器規則更新參數optimizer.step()# 累加當前批次的損失值(loss.item()轉換為Python數值)total_loss += loss.item()# 使用torch.argmax在最后一個維度上取最大值的索引,得到每個樣本預測的類別preds = torch.argmax(output, dim=-1)# 比較預測類別與真實標簽,統計當前批次中預測正確的樣本數,并累加到total_correcttotal_correct += (preds == y).sum().item()# 累加當前批次的樣本數total_samples += len(y)# 計算本Epoch的平均訓練損失:總損失除以批次數(即train_loader中批次數量)avg_train_loss = total_loss / len(train_loader)# 計算本Epoch的訓練準確率:正確預測樣本數除以總樣本數train_accuracy = total_correct / total_samples# 在驗證集上評估模型性能,調用validate_model函數返回驗證集平均損失和準確率avg_valid_loss, valid_accuracy = validate_model(model, valid_loader, criterion, device)# 將本Epoch的訓練和驗證損失、準確率分別記錄到對應的列表中,便于后續繪圖和分析train_losses.append(avg_train_loss)valid_losses.append(avg_valid_loss)train_accuracies.append(train_accuracy)valid_accuracies.append(valid_accuracy)# 如果當前Epoch的驗證準確率優于歷史最佳驗證準確率,則更新best_acc并保存當前模型參數if valid_accuracy > best_acc:best_acc = valid_accuracytorch.save(model.state_dict(), best_model_path, _use_new_zipfile_serialization=True)# 計算本Epoch耗時:當前時間減去開始時間elapsed = time.time() - start_time# 打印本Epoch的訓練信息,包含訓練損失、訓練準確率、驗證損失、驗證準確率和耗時print(f"Epoch [{epoch_idx+1}/{EPOCHS}] "f"訓練損失: {avg_train_loss:.4f}, 訓練準確率: {train_accuracy:.2f}, "f"驗證損失: {avg_valid_loss:.4f}, 驗證準確率: {valid_accuracy:.2f}, "f"耗時: {elapsed:.2f}s")# 訓練結束后,將最終模型(最后一次訓練得到的參數)保存到指定文件中final_model_path = './深度學習實戰/model/image_classification/image_classification_last.pth'torch.save(model.state_dict(), final_model_path, _use_new_zipfile_serialization=True)print(f"訓練完成!最優驗證準確率: {best_acc:.2f}, 模型已保存至: {best_model_path} 和 {final_model_path}")# 返回訓練過程中每個Epoch的訓練/驗證損失和訓練/驗證準確率,方便后續進行曲線可視化return train_losses, valid_losses, train_accuracies, valid_accuraciesdef plot_training_curves(train_losses, valid_losses, train_accs, valid_accs):"""可視化訓練過程中每個Epoch的損失曲線和準確率曲線。參數:train_losses (list): 一個列表,保存了每個Epoch的訓練損失值valid_losses (list): 一個列表,保存了每個Epoch的驗證損失值train_accs (list): 一個列表,保存了每個Epoch的訓練準確率valid_accs (list): 一個列表,保存了每個Epoch的驗證準確率"""# 創建一個迭代器,從1開始到訓練輪數(Epoch)+1# epochs_range 中的每個值代表一個Epoch的編號epochs_range = range(1, len(train_losses) + 1)# 創建一個包含1行2列子圖的畫布,畫布大小設定為寬12英寸,高5英寸# 這樣可以在同一畫布上并排顯示兩個圖,左邊顯示Loss曲線,右邊顯示Accuracy曲線fig, axs = plt.subplots(1, 2, figsize=(12, 5))# 繪制左側子圖:Loss曲線# 在左側子圖上繪制訓練Loss曲線,使用圓形標記('o')axs[0].plot(epochs_range, train_losses, label='訓練Loss', marker='o')# 同樣在左側子圖上繪制驗證Loss曲線,使用叉形標記('x')axs[0].plot(epochs_range, valid_losses, label='驗證Loss', marker='x')# 設置X軸的標簽為"訓練輪數"axs[0].set_xlabel('訓練輪數')# 設置Y軸的標簽為"Loss"axs[0].set_ylabel('Loss')# 設置子圖的標題為"訓練 & 驗證 Loss"axs[0].set_title('訓練 & 驗證 Loss')# 在左側子圖中顯示圖例,圖例會自動顯示各曲線對應的label名稱axs[0].legend()# 繪制右側子圖:Accuracy曲線# 在右側子圖上繪制訓練準確率曲線,使用圓形標記('o')axs[1].plot(epochs_range, train_accs, label='訓練Accuracy', marker='o')# 在右側子圖上繪制驗證準確率曲線,使用叉形標記('x')axs[1].plot(epochs_range, valid_accs, label='驗證Accuracy', marker='x')# 設置X軸的標簽為"訓練輪數"axs[1].set_xlabel('訓練輪數')# 設置Y軸的標簽為"Accuracy"axs[1].set_ylabel('Accuracy')# 設置子圖的標題為"訓練 & 驗證 Accuracy"axs[1].set_title('訓練 & 驗證 Accuracy')# 在右側子圖中顯示圖例axs[1].legend()# 調整子圖布局,使其不會重疊,并使整個畫布看起來更緊湊plt.tight_layout()# 顯示圖像plt.show()def test_model_and_confusion(valid_dataset, device):"""使用驗證集上表現最優的模型,在測試集上評估準確率,并生成混淆矩陣。參數:valid_dataset (Dataset): 驗證集或測試集數據集device (torch.device): 指定的設備(如 CPU、GPU 或 MPS),用于模型和數據的遷移"""# 利用 DataLoader 構建驗證集的批次數據加載器# batch_size 為全局變量 BATCH_SIZE,shuffle 設置為 False 保持順序(便于統計和混淆矩陣計算)valid_loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False)# 指定保存最優模型的路徑best_model_path = './深度學習實戰/model/image_classification/image_classification_best.pth'# 實例化 ImageClassification 模型對象(必須保證模型結構與訓練時一致)model = ImageClassification()# 加載保存的最佳模型參數# torch.load(best_model_path) 從指定路徑加載保存的模型參數(state_dict)# model.load_state_dict(...) 將加載的參數應用到當前模型中model.load_state_dict(torch.load(best_model_path))model = model.to(device)# 設置模型為評估模式# eval() 會使模型進入推理狀態,關閉 Dropout、使 BatchNorm 使用訓練時計算的均值和方差等model.eval()# 初始化統計變量,用于累加預測正確的樣本數和總樣本數total_correct = 0total_samples = 0# 初始化兩個列表,分別用于記錄所有預測的標簽和真實標簽all_preds = []all_labels = []# 使用 torch.no_grad() 表示在驗證和測試階段不計算梯度,降低內存占用、提高推理速度with torch.no_grad():# 遍歷驗證數據加載器中的每個批次for x, y in valid_loader:# 將當前批次的輸入數據 x 和標簽 y 遷移到指定設備上(例如 GPU 或 MPS)x, y = x.to(device), y.to(device)# 將輸入數據傳入模型進行前向傳播,得到輸出結果 output# output 的形狀通常為 (batch_size, num_classes)output = model(x)# 通過 torch.argmax 從 output 中選取得分最大的索引作為預測的類別# 這里 dim=-1 表示沿著最后一個維度進行比較,即每個樣本在所有類別上的得分preds = torch.argmax(output, dim=-1)# 將當前批次的預測結果轉換為 CPU 上的 NumPy 數組,并追加到 all_preds 列表中# 這樣做便于后續使用 scikit-learn 計算混淆矩陣all_preds.extend(preds.cpu().numpy())# 同理,將當前批次的真實標簽轉換為 NumPy 數組并追加到 all_labels 列表中all_labels.extend(y.cpu().numpy())# 比較預測結果與真實標簽,統計預測正確的樣本數# (preds == y) 會生成一個布爾張量,.sum() 求和(True 計為1),再用 .item() 轉為 Python 數值total_correct += (preds == y).sum().item()# 累計本批次的樣本數(len(y) 返回當前批次中樣本的數量)total_samples += len(y)# 計算整體準確率:正確預測的樣本數除以總樣本數accuracy = total_correct / total_samples# 打印在測試集上的準確率,格式化為百分比,保留兩位小數print(f"使用最優模型在測試集上的準確率: {accuracy * 100:.2f}%")# 利用 scikit-learn 的 confusion_matrix 函數計算混淆矩陣# 參數 all_labels 為真實標簽列表,all_preds 為模型預測的標簽列表cm = confusion_matrix(all_labels, all_preds)# 利用 ConfusionMatrixDisplay 將混淆矩陣可視化# display_labels=range(10) 表示顯示 0~9 共10個類別的標簽disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=range(10))# 繪制混淆矩陣圖像,cmap=plt.cm.Blues 設置配色方案為藍色漸變disp.plot(cmap=plt.cm.Blues)# 為混淆矩陣圖添加標題plt.title("CIFAR10 混淆矩陣")# 顯示混淆矩陣圖plt.show()if __name__ == '__main__':import os# 判斷是否在遠程服務器環境if os.path.exists("/home/ubuntu/data/pycharm_project_377"):# 遠程服務器環境os.chdir("/home/ubuntu/data/pycharm_project_377/GPU_32_pythonProject")print(os.getcwd())else:# 本地環境os.chdir("/Users/coyi/PycharmProjects/GPU_32_pythonProject")print(os.getcwd())# 從這里開始,你原有的代碼無需修改# 假設后續代碼中有 ./model/image_classification 之類的相對路徑# 它們都會基于你上面 chdir 到的目錄進行解析# 檢查 GPU 是否可用;如果沒有,則檢查蘋果 MPS 是否可用;否則使用 CPUif torch.cuda.is_available():device = torch.device("cuda")elif torch.backends.mps.is_available():device = torch.device("mps")else:device = torch.device("cpu")print("使用的設備:", device)# 1. 加載數據集并查看基本信息train_dataset, valid_dataset = create_dataset()print("訓練集類別映射:", train_dataset.class_to_idx)print("訓練集數據形狀:", train_dataset.data.shape)print("測試集數據形狀:", valid_dataset.data.shape)# 2. 可視化一張訓練集圖片plt.figure(figsize=(2, 2))plt.imshow(train_dataset.data[1])plt.title(f"類別索引: {train_dataset.targets[1]}")plt.show()# 3. 實例化網絡并查看結構model = ImageClassification().to(device)summary(model, input_size=(3, 32, 32), batch_size=1)# model = model# 4. 訓練模型并記錄曲線數據train_losses, valid_losses, train_accs, valid_accs = train_model(model, train_dataset, valid_dataset, device)# 5. 繪制訓練過程曲線plot_training_curves(train_losses, valid_losses, train_accs, valid_accs)# 6. 使用最佳模型進行測試并展示混淆矩陣test_model_and_confusion(valid_dataset, device)

代碼1輸出:

?


代碼2: 使用ResNet34 自己從0訓練權重,未優化,未使用原始權重

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor, Composeimport torchvision.models as models  # 用于加載官方resnet34
import time
import matplotlib.pyplot as plt
from torchsummary import summary# 方便中文顯示的 matplotlib 配置
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = Falseimport numpy as np
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay# ---------------------------
# 1. 全局超參數
# ---------------------------
BATCH_SIZE = 8            # 每批處理多少張圖
EPOCHS = 100              # 訓練輪數
LEARNING_RATE = 1e-3      # 學習率# ---------------------------
# 2. 數據集加載函數
# ---------------------------
def create_dataset():"""加載 CIFAR-10 數據集。CIFAR-10:訓練集50000張,測試集10000張,圖像32x32,RGB三通道,分成10類。ToTensor() 會把 [0,255] 的像素值歸一化到 [0,1] 的 float32 張量。download=True 會自動下載到根目錄 data/ 下。"""train = CIFAR10(root='data',        # 指定下載/存儲路徑train=True,         # True表示訓練集transform=Compose([ToTensor()]),  # 將圖像轉為張量download=True       # 若本地無數據則自動下載)valid = CIFAR10(root='data',train=False,        # False表示測試集transform=Compose([ToTensor()]),download=True)return train, valid# ---------------------------
# 3. 構建 resnet34 模型并適配 CIFAR-10
# ---------------------------
def get_resnet34_cifar10():"""使用官方 torchvision.models.resnet34(pretrained=False)。1) 將 conv1 改成 3x3, stride=1, padding=12) 將 maxpool 改成 Identity() => 不執行池化3) 將 fc 改成輸出通道數=10(CIFAR10有10類)"""net = models.resnet34(pretrained=False)  # 不使用ImageNet預訓練權重# 修改第一層卷積 => 原本7x7, stride=2,容易把32x32圖像縮得太小net.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)# 去掉 maxpool => 不進行額外下采樣net.maxpool = nn.Identity()# 最后全連接層 => 改為輸出10類net.fc = nn.Linear(in_features=512, out_features=10, bias=True)return net# ---------------------------
# 4. 驗證過程
# ---------------------------
def validate_model(model, valid_loader, criterion, device):"""在驗證集/測試集上計算Loss和Accuracy,用于評估模型性能。model: 當前的神經網絡valid_loader: 驗證集DataLoadercriterion: 損失函數device: 'cuda' 或 'cpu'返回: (平均loss, 準確率)"""model.eval()  # 切換到評估模式,關閉Dropout等隨機操作total_loss = 0.0total_correct = 0total_samples = 0with torch.no_grad():for x, y in valid_loader:x, y = x.to(device), y.to(device)  # 將數據放到指定設備上output = model(x)                 # 前向傳播loss = criterion(output, y)       # 計算損失total_loss += loss.item()# argmax => 找到輸出張量中得分最大的類別preds = torch.argmax(output, dim=-1)total_correct += (preds == y).sum().item()total_samples += len(y)avg_loss = total_loss / len(valid_loader)accuracy = total_correct / total_samplesreturn avg_loss, accuracy# ---------------------------
# 5. 訓練過程
# ---------------------------
def train_model(model, train_dataset, valid_dataset, device):"""訓練并驗證模型。若發現更高驗證集準確率則保存最優權重。返回各Epoch的Loss和Accuracy,供繪圖使用。"""criterion = nn.CrossEntropyLoss()                     # 交叉熵損失optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)  # AdamW優化器train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)valid_loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False)train_losses, valid_losses = [], []train_accs, valid_accs = [], []best_acc = 0.0# 當驗證集準確率超過之前最優時,保存到 best_model_pathbest_model_path = './深度學習實戰/model/resnet34_cifar10_best.pth'for epoch_idx in range(EPOCHS):model.train()  # 切換到訓練模式start_time = time.time()total_loss = 0.0total_correct = 0total_samples = 0for x, y in train_loader:x, y = x.to(device), y.to(device)output = model(x)         # 前向loss = criterion(output, y)optimizer.zero_grad()     # 梯度清零loss.backward()           # 反向傳播optimizer.step()          # 更新參數total_loss += loss.item()preds = torch.argmax(output, dim=-1)total_correct += (preds == y).sum().item()total_samples += len(y)avg_train_loss = total_loss / len(train_loader)train_acc = total_correct / total_samples# 在驗證集上評估avg_valid_loss, valid_acc = validate_model(model, valid_loader, criterion, device)train_losses.append(avg_train_loss)valid_losses.append(avg_valid_loss)train_accs.append(train_acc)valid_accs.append(valid_acc)# 如果當前驗證準確率超過歷史最優 => 保存if valid_acc > best_acc:best_acc = valid_acctorch.save(model.state_dict(), best_model_path)elapsed = time.time() - start_timeprint(f"Epoch [{epoch_idx+1}/{EPOCHS}] "f"Train Loss: {avg_train_loss:.4f}, Train Acc: {train_acc:.2f}, "f"Valid Loss: {avg_valid_loss:.4f}, Valid Acc: {valid_acc:.2f}, "f"Time: {elapsed:.2f}s")# 最后再保存一次final_path = './深度學習實戰/model/resnet34_cifar10_last.pth'torch.save(model.state_dict(), final_path)print(f"訓練完成!最優驗證準確率: {best_acc:.2f}. 模型已保存到 {best_model_path} 和 {final_path}")return train_losses, valid_losses, train_accs, valid_accs# ---------------------------
# 6. 繪制訓練過程曲線
# ---------------------------
def plot_training_curves(train_losses, valid_losses, train_accs, valid_accs):"""傳入訓練/驗證Loss和Acc的列表,畫出隨Epoch變化的曲線。"""epochs_range = range(1, len(train_losses)+1)fig, axs = plt.subplots(1, 2, figsize=(12,5))# 左邊畫 Lossaxs[0].plot(epochs_range, train_losses, label='Train Loss', marker='o')axs[0].plot(epochs_range, valid_losses, label='Valid Loss', marker='x')axs[0].set_xlabel('Epochs')axs[0].set_ylabel('Loss')axs[0].set_title('Train & Valid Loss')axs[0].legend()# 右邊畫 Accuracyaxs[1].plot(epochs_range, train_accs, label='Train Acc', marker='o')axs[1].plot(epochs_range, valid_accs, label='Valid Acc', marker='x')axs[1].set_xlabel('Epochs')axs[1].set_ylabel('Accuracy')axs[1].set_title('Train & Valid Accuracy')axs[1].legend()plt.tight_layout()plt.show()# ---------------------------
# 7. 使用最佳模型 + 混淆矩陣
# ---------------------------
def test_model_and_confusion(valid_dataset, device):"""加載最優模型,在測試集上計算準確率并繪制混淆矩陣。"""loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False)# 初始化網絡 => 需要跟訓練時的結構一致model = get_resnet34_cifar10()best_model_path = './深度學習實戰/model/resnet34_cifar10_best.pth'model.load_state_dict(torch.load(best_model_path))  # 加載最佳權重model = model.to(device)model.eval()total_correct = 0total_samples = 0all_preds = []all_labels = []with torch.no_grad():for x, y in loader:x, y = x.to(device), y.to(device)output = model(x)preds = torch.argmax(output, dim=-1)# 用于混淆矩陣all_preds.extend(preds.cpu().numpy())all_labels.extend(y.cpu().numpy())total_correct += (preds == y).sum().item()total_samples += len(y)acc = total_correct / total_samplesprint(f"使用最優模型在測試集上的準確率: {acc*100:.2f}%")# 混淆矩陣cm = confusion_matrix(all_labels, all_preds)disp = ConfusionMatrixDisplay(cm, display_labels=range(10))disp.plot(cmap=plt.cm.Blues)plt.title("resnet34 on CIFAR10 Confusion Matrix")plt.show()# ---------------------------
# 8. 主函數入口
# ---------------------------
if __name__ == '__main__':import os# 檢查是遠程路徑還是本地路徑if os.path.exists("/home/ubuntu/data/pycharm_project_377"):os.chdir("/home/ubuntu/data/pycharm_project_377/GPU_32_pythonProject")print("當前工作目錄(遠程):", os.getcwd())else:os.chdir("/Users/coyi/PycharmProjects/GPU_32_pythonProject")print("當前工作目錄(本地):", os.getcwd())# 檢查設備 => 優先用 GPUif torch.cuda.is_available():device = torch.device("cuda:0")else:device = torch.device("cpu")print("使用的設備:", device)# (1) 加載CIFAR10train_dataset, valid_dataset = create_dataset()print("訓練集類別映射:", train_dataset.class_to_idx)print("訓練集數據形狀:", train_dataset.data.shape)print("測試集數據形狀:", valid_dataset.data.shape)# (2) 可視化一張圖plt.figure()plt.imshow(train_dataset.data[0])plt.title(f"標簽: {train_dataset.targets[0]}")plt.show()# (3) 實例化 resnet34 并查看結構model = get_resnet34_cifar10().to(device)summary(model, input_size=(3, 32, 32), batch_size=1)# (4) 訓練train_losses, valid_losses, train_accs, valid_accs = train_model(model, train_dataset, valid_dataset, device)# (5) 可視化訓練plot_training_curves(train_losses, valid_losses, train_accs, valid_accs)# (6) 測試集上做預測 + 混淆矩陣test_model_and_confusion(valid_dataset, device)

?輸出:

然后被我KIll了,因為太慢了


代碼3: 使用ResNet34 自己從0訓練權重,優化訓練過程,未使用原始權重

代碼是在代碼2的基礎上加了優化過程

import torchtorch.backends.cudnn.benchmark = True #torch.backends.cudnn.benchmark = True 會自動搜索最佳的卷積算法,當輸入尺寸固定時可以大幅加速卷積計算。
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor, Composeimport torchvision.models as models  # 用于加載官方resnet34
import time
import matplotlib.pyplot as plt
from torchsummary import summary# 方便中文顯示的 matplotlib 配置
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = Falseimport numpy as np
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay# ---------------------------
# 1. 全局超參數
# ---------------------------
BATCH_SIZE = 256            # 每批處理多少張圖
EPOCHS = 100              # 訓練輪數
LEARNING_RATE = 1e-3      # 學習率# ---------------------------
# 2. 數據集加載函數
# ---------------------------
def create_dataset():"""加載 CIFAR-10 數據集。CIFAR-10:訓練集50000張,測試集10000張,圖像32x32,RGB三通道,分成10類。ToTensor() 會把 [0,255] 的像素值歸一化到 [0,1] 的 float32 張量。download=True 會自動下載到根目錄 data/ 下。"""train = CIFAR10(root='data',        # 指定下載/存儲路徑train=True,         # True表示訓練集transform=Compose([ToTensor()]),  # 將圖像轉為張量download=True       # 若本地無數據則自動下載)valid = CIFAR10(root='data',train=False,        # False表示測試集transform=Compose([ToTensor()]),download=True)return train, valid# ---------------------------
# 3. 構建 resnet34 模型并適配 CIFAR-10
# ---------------------------
def get_resnet34_cifar10():"""使用官方 torchvision.models.resnet34(pretrained=False)。1) 將 conv1 改成 3x3, stride=1, padding=12) 將 maxpool 改成 Identity() => 不執行池化3) 將 fc 改成輸出通道數=10(CIFAR10有10類)"""net = models.resnet34(pretrained=False)  # 不使用ImageNet預訓練權重# 修改第一層卷積 => 原本7x7, stride=2,容易把32x32圖像縮得太小net.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)# 去掉 maxpool => 不進行額外下采樣net.maxpool = nn.Identity()# 最后全連接層 => 改為輸出10類net.fc = nn.Linear(in_features=512, out_features=10, bias=True)return net# ---------------------------
# 4. 驗證過程
# ---------------------------
def validate_model(model, valid_loader, criterion, device):"""在驗證集/測試集上計算Loss和Accuracy,用于評估模型性能。model: 當前的神經網絡valid_loader: 驗證集DataLoadercriterion: 損失函數device: 'cuda' 或 'cpu'返回: (平均loss, 準確率)"""model.eval()  # 切換到評估模式,關閉Dropout等隨機操作total_loss = 0.0total_correct = 0total_samples = 0with torch.no_grad():for x, y in valid_loader:x, y = x.to(device), y.to(device)  # 將數據放到指定設備上output = model(x)                 # 前向傳播loss = criterion(output, y)       # 計算損失total_loss += loss.item()# argmax => 找到輸出張量中得分最大的類別preds = torch.argmax(output, dim=-1)total_correct += (preds == y).sum().item()total_samples += len(y)avg_loss = total_loss / len(valid_loader)accuracy = total_correct / total_samplesreturn avg_loss, accuracy# ---------------------------
# 5. 訓練過程
# ---------------------------
def train_model(model, train_dataset, valid_dataset, device):"""訓練并驗證模型。若發現更高驗證集準確率則保存最優權重。返回各Epoch的Loss和Accuracy,供繪圖使用。"""criterion = nn.CrossEntropyLoss()                     # 交叉熵損失optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)  # AdamW優化器# 優化 DataLoader,數值 num_workers 可根據 CPU 核心數進行調整。# ?	增加 num_workers:DataLoader 的 num_workers 參數決定了數據預處理的并行程度。增大 num_workers 可以加快數據加載速度,避免因數據加載速度過慢而使 GPU 等待數據。# ?	設置 pin_memory=True:對于 GPU 訓練,設置 pin_memory=True 可以加速數據從主機內存傳輸到 GPU。train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=8, pin_memory=True)valid_loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=8, pin_memory=True)train_losses, valid_losses = [], []train_accs, valid_accs = [], []best_acc = 0.0# 當驗證集準確率超過之前最優時,保存到 best_model_pathbest_model_path = './深度學習實戰/model/resnet34_cifar10_best.pth'scaler = torch.amp.GradScaler()  # 混合精度梯度縮放器for epoch_idx in range(EPOCHS):model.train()  # 切換到訓練模式start_time = time.time()total_loss = 0.0total_correct = 0total_samples = 0for x, y in train_loader:x, y = x.to(device), y.to(device)# output = model(x)         # 前向# loss = criterion(output, y)# optimizer.zero_grad()     # 梯度清零# loss.backward()           # 反向傳播# optimizer.step()          # 更新參數optimizer.zero_grad()# 自動混合精度上下文with torch.amp.autocast(device_type=device.type):output = model(x)loss = criterion(output, y)scaler.scale(loss).backward()scaler.step(optimizer)scaler.update()total_loss += loss.item()preds = torch.argmax(output, dim=-1)total_correct += (preds == y).sum().item()total_samples += len(y)avg_train_loss = total_loss / len(train_loader)train_acc = total_correct / total_samples# 在驗證集上評估avg_valid_loss, valid_acc = validate_model(model, valid_loader, criterion, device)train_losses.append(avg_train_loss)valid_losses.append(avg_valid_loss)train_accs.append(train_acc)valid_accs.append(valid_acc)# 如果當前驗證準確率超過歷史最優 => 保存if valid_acc > best_acc:best_acc = valid_acctorch.save(model.state_dict(), best_model_path)elapsed = time.time() - start_timeprint(f"Epoch [{epoch_idx+1}/{EPOCHS}] "f"Train Loss: {avg_train_loss:.4f}, Train Acc: {train_acc:.2f}, "f"Valid Loss: {avg_valid_loss:.4f}, Valid Acc: {valid_acc:.2f}, "f"Time: {elapsed:.2f}s")# 最后再保存一次final_path = './深度學習實戰/model/resnet34_cifar10_last.pth'torch.save(model.state_dict(), final_path)print(f"訓練完成!最優驗證準確率: {best_acc:.2f}. 模型已保存到 {best_model_path} 和 {final_path}")return train_losses, valid_losses, train_accs, valid_accs# ---------------------------
# 6. 繪制訓練過程曲線
# ---------------------------
def plot_training_curves(train_losses, valid_losses, train_accs, valid_accs):"""傳入訓練/驗證Loss和Acc的列表,畫出隨Epoch變化的曲線。"""epochs_range = range(1, len(train_losses)+1)fig, axs = plt.subplots(1, 2, figsize=(12,5))# 左邊畫 Lossaxs[0].plot(epochs_range, train_losses, label='Train Loss', marker='o')axs[0].plot(epochs_range, valid_losses, label='Valid Loss', marker='x')axs[0].set_xlabel('Epochs')axs[0].set_ylabel('Loss')axs[0].set_title('Train & Valid Loss')axs[0].legend()# 右邊畫 Accuracyaxs[1].plot(epochs_range, train_accs, label='Train Acc', marker='o')axs[1].plot(epochs_range, valid_accs, label='Valid Acc', marker='x')axs[1].set_xlabel('Epochs')axs[1].set_ylabel('Accuracy')axs[1].set_title('Train & Valid Accuracy')axs[1].legend()plt.tight_layout()plt.show()# ---------------------------
# 7. 使用最佳模型 + 混淆矩陣
# ---------------------------
def test_model_and_confusion(valid_dataset, device):"""加載最優模型,在測試集上計算準確率并繪制混淆矩陣。"""loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=8, pin_memory=True)# 初始化網絡 => 需要跟訓練時的結構一致model = get_resnet34_cifar10()best_model_path = './深度學習實戰/model/resnet34_cifar10_best.pth'model.load_state_dict(torch.load(best_model_path, weights_only=True))  # 加載最佳權重model = model.to(device)model.eval()total_correct = 0total_samples = 0all_preds = []all_labels = []with torch.no_grad():for x, y in loader:x, y = x.to(device), y.to(device)output = model(x)preds = torch.argmax(output, dim=-1)# 用于混淆矩陣all_preds.extend(preds.cpu().numpy())all_labels.extend(y.cpu().numpy())total_correct += (preds == y).sum().item()total_samples += len(y)acc = total_correct / total_samplesprint(f"使用最優模型在測試集上的準確率: {acc*100:.2f}%")# 混淆矩陣cm = confusion_matrix(all_labels, all_preds)disp = ConfusionMatrixDisplay(cm, display_labels=range(10))disp.plot(cmap=plt.cm.Blues)plt.title("resnet34 on CIFAR10 Confusion Matrix")plt.show()# ---------------------------
# 8. 主函數入口
# ---------------------------
if __name__ == '__main__':import os# 檢查是遠程路徑還是本地路徑if os.path.exists("/home/ubuntu/data/pycharm_project_377"):os.chdir("/home/ubuntu/data/pycharm_project_377/GPU_32_pythonProject")print("當前工作目錄(遠程):", os.getcwd())else:os.chdir("/Users/coyi/PycharmProjects/GPU_32_pythonProject")print("當前工作目錄(本地):", os.getcwd())# 檢查設備 => 優先用 GPUif torch.cuda.is_available():device = torch.device("cuda:0")else:device = torch.device("cpu")print("使用的設備:", device)# (1) 加載CIFAR10train_dataset, valid_dataset = create_dataset()print("訓練集類別映射:", train_dataset.class_to_idx)print("訓練集數據形狀:", train_dataset.data.shape)print("測試集數據形狀:", valid_dataset.data.shape)# (2) 可視化一張圖plt.figure()plt.imshow(train_dataset.data[0])plt.title(f"標簽: {train_dataset.targets[0]}")plt.show()# (3) 實例化 resnet34 并查看結構model = get_resnet34_cifar10().to(device)summary(model, input_size=(3, 32, 32), batch_size=1)# (4) 訓練train_losses, valid_losses, train_accs, valid_accs = train_model(model, train_dataset, valid_dataset, device)# (5) 可視化訓練plot_training_curves(train_losses, valid_losses, train_accs, valid_accs)# (6) 測試集上做預測 + 混淆矩陣test_model_and_confusion(valid_dataset, device)

輸出:

?


代碼4: 使用ResNet34 的預訓練權重,優化訓練過程

在代碼3的基礎上,使用了resnet34的原始權重的基礎上進行訓練

import torchtorch.backends.cudnn.benchmark = True #torch.backends.cudnn.benchmark = True 會自動搜索最佳的卷積算法,當輸入尺寸固定時可以大幅加速卷積計算。
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor, Composeimport torchvision.models as models  # 用于加載官方resnet34
import time
import matplotlib.pyplot as plt
from torchsummary import summary# 方便中文顯示的 matplotlib 配置
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = Falseimport numpy as np
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay# ---------------------------
# 1. 全局超參數
# ---------------------------
BATCH_SIZE = 256            # 每批處理多少張圖
EPOCHS = 100              # 訓練輪數
LEARNING_RATE = 1e-3      # 學習率# ---------------------------
# 2. 數據集加載函數
# ---------------------------
def create_dataset():"""加載 CIFAR-10 數據集。CIFAR-10:訓練集50000張,測試集10000張,圖像32x32,RGB三通道,分成10類。ToTensor() 會把 [0,255] 的像素值歸一化到 [0,1] 的 float32 張量。download=True 會自動下載到根目錄 data/ 下。"""train = CIFAR10(root='data',        # 指定下載/存儲路徑train=True,         # True表示訓練集transform=Compose([ToTensor()]),  # 將圖像轉為張量download=True       # 若本地無數據則自動下載)valid = CIFAR10(root='data',train=False,        # False表示測試集transform=Compose([ToTensor()]),download=True)return train, valid# ---------------------------
# 3. 構建 resnet34 模型并適配 CIFAR-10
# ---------------------------
def get_resnet34_cifar10():"""使用官方 torchvision.models.resnet34(pretrained=False)。1) 將 conv1 改成 3x3, stride=1, padding=12) 將 maxpool 改成 Identity() => 不執行池化3) 將 fc 改成輸出通道數=10(CIFAR10有10類)"""net = models.resnet34(weights=models.ResNet34_Weights.IMAGENET1K_V1)  # 使用ImageNet預訓練權重# 修改第一層卷積 => 原本7x7, stride=2,容易把32x32圖像縮得太小net.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)# 去掉 maxpool => 不進行額外下采樣net.maxpool = nn.Identity()# 最后全連接層 => 改為輸出10類net.fc = nn.Linear(in_features=512, out_features=10, bias=True)return net# ---------------------------
# 4. 驗證過程
# ---------------------------
def validate_model(model, valid_loader, criterion, device):"""在驗證集/測試集上計算Loss和Accuracy,用于評估模型性能。model: 當前的神經網絡valid_loader: 驗證集DataLoadercriterion: 損失函數device: 'cuda' 或 'cpu'返回: (平均loss, 準確率)"""model.eval()  # 切換到評估模式,關閉Dropout等隨機操作total_loss = 0.0total_correct = 0total_samples = 0with torch.no_grad():for x, y in valid_loader:x, y = x.to(device), y.to(device)  # 將數據放到指定設備上output = model(x)                 # 前向傳播loss = criterion(output, y)       # 計算損失total_loss += loss.item()# argmax => 找到輸出張量中得分最大的類別preds = torch.argmax(output, dim=-1)total_correct += (preds == y).sum().item()total_samples += len(y)avg_loss = total_loss / len(valid_loader)accuracy = total_correct / total_samplesreturn avg_loss, accuracy# ---------------------------
# 5. 訓練過程
# ---------------------------
def train_model(model, train_dataset, valid_dataset, device):"""訓練并驗證模型。若發現更高驗證集準確率則保存最優權重。返回各Epoch的Loss和Accuracy,供繪圖使用。"""criterion = nn.CrossEntropyLoss()                     # 交叉熵損失optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)  # AdamW優化器# 優化 DataLoader,數值 num_workers 可根據 CPU 核心數進行調整。# ?	增加 num_workers:DataLoader 的 num_workers 參數決定了數據預處理的并行程度。增大 num_workers 可以加快數據加載速度,避免因數據加載速度過慢而使 GPU 等待數據。# ?	設置 pin_memory=True:對于 GPU 訓練,設置 pin_memory=True 可以加速數據從主機內存傳輸到 GPU。train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=8, pin_memory=True)valid_loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=8, pin_memory=True)train_losses, valid_losses = [], []train_accs, valid_accs = [], []best_acc = 0.0# 當驗證集準確率超過之前最優時,保存到 best_model_pathbest_model_path = './深度學習實戰/model/resnet34_cifar10_best.pth'scaler = torch.amp.GradScaler()  # 混合精度梯度縮放器for epoch_idx in range(EPOCHS):model.train()  # 切換到訓練模式start_time = time.time()total_loss = 0.0total_correct = 0total_samples = 0for x, y in train_loader:x, y = x.to(device), y.to(device)# output = model(x)         # 前向# loss = criterion(output, y)# optimizer.zero_grad()     # 梯度清零# loss.backward()           # 反向傳播# optimizer.step()          # 更新參數optimizer.zero_grad()# 自動混合精度上下文with torch.amp.autocast(device_type=device.type):output = model(x)loss = criterion(output, y)scaler.scale(loss).backward()scaler.step(optimizer)scaler.update()total_loss += loss.item()preds = torch.argmax(output, dim=-1)total_correct += (preds == y).sum().item()total_samples += len(y)avg_train_loss = total_loss / len(train_loader)train_acc = total_correct / total_samples# 在驗證集上評估avg_valid_loss, valid_acc = validate_model(model, valid_loader, criterion, device)train_losses.append(avg_train_loss)valid_losses.append(avg_valid_loss)train_accs.append(train_acc)valid_accs.append(valid_acc)# 如果當前驗證準確率超過歷史最優 => 保存if valid_acc > best_acc:best_acc = valid_acctorch.save(model.state_dict(), best_model_path)elapsed = time.time() - start_timeprint(f"Epoch [{epoch_idx+1}/{EPOCHS}] "f"Train Loss: {avg_train_loss:.4f}, Train Acc: {train_acc:.2f}, "f"Valid Loss: {avg_valid_loss:.4f}, Valid Acc: {valid_acc:.2f}, "f"Time: {elapsed:.2f}s")# 最后再保存一次final_path = './深度學習實戰/model/resnet34_cifar10_last.pth'torch.save(model.state_dict(), final_path)print(f"訓練完成!最優驗證準確率: {best_acc:.2f}. 模型已保存到 {best_model_path} 和 {final_path}")return train_losses, valid_losses, train_accs, valid_accs# ---------------------------
# 6. 繪制訓練過程曲線
# ---------------------------
def plot_training_curves(train_losses, valid_losses, train_accs, valid_accs):"""傳入訓練/驗證Loss和Acc的列表,畫出隨Epoch變化的曲線。"""epochs_range = range(1, len(train_losses)+1)fig, axs = plt.subplots(1, 2, figsize=(12,5))# 左邊畫 Lossaxs[0].plot(epochs_range, train_losses, label='Train Loss', marker='o')axs[0].plot(epochs_range, valid_losses, label='Valid Loss', marker='x')axs[0].set_xlabel('Epochs')axs[0].set_ylabel('Loss')axs[0].set_title('Train & Valid Loss')axs[0].legend()# 右邊畫 Accuracyaxs[1].plot(epochs_range, train_accs, label='Train Acc', marker='o')axs[1].plot(epochs_range, valid_accs, label='Valid Acc', marker='x')axs[1].set_xlabel('Epochs')axs[1].set_ylabel('Accuracy')axs[1].set_title('Train & Valid Accuracy')axs[1].legend()plt.tight_layout()plt.show()# ---------------------------
# 7. 使用最佳模型 + 混淆矩陣
# ---------------------------
def test_model_and_confusion(valid_dataset, device):"""加載最優模型,在測試集上計算準確率并繪制混淆矩陣。"""loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=8, pin_memory=True)# 初始化網絡 => 需要跟訓練時的結構一致model = get_resnet34_cifar10()best_model_path = './深度學習實戰/model/resnet34_cifar10_best.pth'model.load_state_dict(torch.load(best_model_path, weights_only=True))  # 加載最佳權重model = model.to(device)model.eval()total_correct = 0total_samples = 0all_preds = []all_labels = []with torch.no_grad():for x, y in loader:x, y = x.to(device), y.to(device)output = model(x)preds = torch.argmax(output, dim=-1)# 用于混淆矩陣all_preds.extend(preds.cpu().numpy())all_labels.extend(y.cpu().numpy())total_correct += (preds == y).sum().item()total_samples += len(y)acc = total_correct / total_samplesprint(f"使用最優模型在測試集上的準確率: {acc*100:.2f}%")# 混淆矩陣cm = confusion_matrix(all_labels, all_preds)disp = ConfusionMatrixDisplay(cm, display_labels=range(10))disp.plot(cmap=plt.cm.Blues)plt.title("resnet34 on CIFAR10 Confusion Matrix")plt.show()# ---------------------------
# 8. 主函數入口
# ---------------------------
if __name__ == '__main__':import os# 檢查是遠程路徑還是本地路徑if os.path.exists("/home/ubuntu/data/pycharm_project_377"):os.chdir("/home/ubuntu/data/pycharm_project_377/GPU_32_pythonProject")print("當前工作目錄(遠程):", os.getcwd())else:os.chdir("/Users/coyi/PycharmProjects/GPU_32_pythonProject")print("當前工作目錄(本地):", os.getcwd())# 檢查設備 => 優先用 GPUif torch.cuda.is_available():device = torch.device("cuda:0")else:device = torch.device("cpu")print("使用的設備:", device)# (1) 加載CIFAR10train_dataset, valid_dataset = create_dataset()print("訓練集類別映射:", train_dataset.class_to_idx)print("訓練集數據形狀:", train_dataset.data.shape)print("測試集數據形狀:", valid_dataset.data.shape)# (2) 可視化一張圖plt.figure()plt.imshow(train_dataset.data[0])plt.title(f"標簽: {train_dataset.targets[0]}")plt.show()# (3) 實例化 resnet34 并查看結構model = get_resnet34_cifar10().to(device)summary(model, input_size=(3, 32, 32), batch_size=1)# (4) 訓練train_losses, valid_losses, train_accs, valid_accs = train_model(model, train_dataset, valid_dataset, device)# (5) 可視化訓練plot_training_curves(train_losses, valid_losses, train_accs, valid_accs)# (6) 測試集上做預測 + 混淆矩陣test_model_and_confusion(valid_dataset, device)

輸出:

?


總結與展望

本文以 CIFAR-10 為例,提供了四 份完整可運行的 PyTorch 代碼,分別展示了以下四個層次的圖像分類實戰案例:

? 代碼一:自定義 CNN

介紹了如何手工設計一個卷積神經網絡,突出基礎原理與手工實現,讓你真正理解卷積神經網絡的構造和各層設計的作用。

? 代碼二:使用開源模型 ResNet34

演示如何快速拿來使用經過 ImageNet 預訓練的經典模型,并做少量調整(如修改第一層卷積和全連接層)以適應 CIFAR-10 的數據和分類任務,從而得到更高的準確率。

? 代碼三:高效訓練優化

針對訓練效率問題,采用了多線程加載、混合精度訓練(AMP)、cuDNN 加速,提高批次 等策略,大幅提升訓練速度同時保證精度,最終案例準確率達到了 87.57%, 單次訓練時長12秒。

? 代碼四:使用開源模型 ResNet34 的原生權重微調

最終案例準確率達到了 90。24%, 單次訓練時長12秒。完成了需求

通過這篇文章,你已經具備了獨立實現或改造 CNN 分類項目的能力,并能舉一反三,將這些方法拓展到更大規模或更復雜的項目中。未來可以嘗試加入更多優化技巧,如模型壓縮、數據增強等,進一步提高訓練效率和模型準確率。


參考文獻/引用來源

1. PyTorch 官方文檔

PyTorch documentation — PyTorch 2.6 documentation

2. torchvision.models 源碼

官方 GitHub 倉庫及文檔中提供了各種預訓練模型的詳細實現。

3. Deep Residual Learning for Image Recognition (ResNet)

He, K., Zhang, X., Ren, S., & Sun, J. (2016). Deep Residual Learning for Image Recognition. arXiv:1512.03385

4. CIFAR-10 數據集

CIFAR-10 and CIFAR-100 datasets — 官方網站


如果你覺得這篇文章對你有所幫助,歡迎你點贊、收藏、評論和轉發支持一下!你的關注與互動將是我持續創作高質量內容的最大動力,也歡迎在評論區分享你訓練的結果和心得,與大家一起交流進步!

希望以上整理能滿足你的需求,感謝閱讀,我們下篇文章再見!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71363.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71363.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71363.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OPPO Find N5折疊手機:創新與實用的完美融合,FPC應用展現科技魅力【新立電子】

OPPO Find N5作為2025年新出世的折疊手機,以其卓越的設計、強大的性能以及創新的技術,為消費者帶來了全新的使用體驗。FPC(柔性電路板)在其中的運用,也進一步提升了手機的整體性能和用戶體驗。 OPPO Find N5的最大亮點…

【AD】PCB增加相關圖層——以機械層為例

問題:圖中PCB僅有機械層1和機械層2,想要在加一個機械層3 解決 1.點擊視圖—面板—View Configuration,選中機械層右鍵單擊增加層,其他層類似

Qt5 C++ QMap使用總結

文章目錄 功能解釋代碼使用案例代碼解釋注意事項代碼例子參考 功能解釋 QList<T> QMap::values() const Returns a list containing all the values in the map, in ascending order of their keys. If a key is associated with multiple values, all of its values wi…

測試用例總結

一、通用測試用例八要素   1、用例編號&#xff1b;    2、測試項目&#xff1b;   3、測試標題&#xff1b; 4、重要級別&#xff1b;    5、預置條件&#xff1b;    6、測試輸入&#xff1b;    7、操作步驟&#xff1b;    8、預期輸出 二、具體分析通…

不用寫代碼,批量下載今日頭條文章導出excel和pdf

前幾天有人問我怎么批量抓取今日頭條某個號的所有文章數據&#xff0c;需要文章鏈接&#xff0c;標題和時間&#xff0c;但是不會寫代碼&#xff0c;于是我寫了個簡單的教程 這里以渤海小吏為例 首先用edge瀏覽器安裝web-scraper瀏覽器擴展 然后打開瀏覽器控制臺&#xff0c;找…

Starrocks 寫入報錯 primary key memory usage exceeds the limit

背景 本文基于 StarRocks 3.3.5 單個Starrocks BE配置是 16CU 32GB 在Flink Yaml CDC 任務往 Starrocks寫數據的過程中&#xff0c;突然遇到了primary key memory usage exceeds the limit 問題&#xff0c;具體如下&#xff1a; java.lang.RuntimeException: com.starrocks.…

Django:文件上傳時報錯in a frame because it set ‘X-Frame-Options‘ to ‘deny‘.

即&#xff1a;使用Content-Security-Policy 1.安裝Django CSP中間件&#xff1a; pip install django-csp 2.更改項目配置&#xff1a; # settings.py MIDDLEWARE [...csp.middleware.CSPMiddleware,... ]CSP_DEFAULT_SRC ("self",) CSP_FRAME_ANCESTORS (&q…

利用Adobe Acrobat 實現PPT中圖片分辨率的提升

1. 下載適用于 Windows 的 64 位 Acrobat 注冊方式參考&#xff1a;https://ca.whu.edu.cn/knowledge.html?type1 2. 將ppt中需要提高分辨率的圖片復制粘貼到新建的pptx問價中&#xff0c;然后執行“文件—>導出---->創建PDF、XPS文檔” 3. 我們會發現保存下來的distrib…

【Python爬蟲】爬取公共交通路網數據

程序來自于Github&#xff0c;以下這篇博客作為完整的學習記錄&#xff0c;也callback上一篇爬取公共交通站點的博文。 Bardbo/get_bus_lines_and_stations_data_from_gaode: 這個項目是基于高德開放平臺和公交網獲取公交線路及站點數據&#xff0c;并生成shp文件&#xff0c;…

Stable Diffusion模型高清算法模型類詳解

Stable Diffusion模型高清算法模型類詳細對比表 模型名稱核心原理適用場景參數建議顯存消耗細節增強度優缺點4x-UltraSharp殘差密集塊(RDB)結構優化紋理生成真實人像/建筑攝影重繪幅度0.3-0.4&#xff0c;分塊尺寸768px★★★★★☆皮膚紋理細膩&#xff0c;但高對比場景易出現…

VUE_使用Vite構建vue項目

創建項目 // 安裝vite npm install vite// 創建名為vite-app的項目 npm create vite vite-app --template vue// 到項目目錄 cd vite-app// 安裝依賴 npm install// 運行項目 npm run dev// 打包 npm run build// 打包預覽 npm run serve 增加路由 // 安裝路由 npm add vue-r…

ctf網絡安全賽題

CTF簡介 CTF&#xff08;Capture The Flag&#xff09;中文一般譯作奪旗賽&#xff0c;在網絡安全領域中指的是網絡安全技術人員之間進行技術競技的一種比賽形式。CTF起源于1996年DEFCON全球黑客大會&#xff0c;以代替之前黑客們通過互相發起真實攻擊進行技術比拼的方式。發展…

【朝夕教育】《鴻蒙原生應用開發從零基礎到多實戰》004-TypeScript 中的泛型

標題詳情作者簡介愚公搬代碼頭銜華為云特約編輯&#xff0c;華為云云享專家&#xff0c;華為開發者專家&#xff0c;華為產品云測專家&#xff0c;CSDN博客專家&#xff0c;CSDN商業化專家&#xff0c;阿里云專家博主&#xff0c;阿里云簽約作者&#xff0c;騰訊云優秀博主&…

性能測試監控工具jmeter+grafana

1、什么是性能測試監控體系&#xff1f; 為什么要有監控體系&#xff1f; 原因&#xff1a; 1、項目-日益復雜&#xff08;內部除了代碼外&#xff0c;還有中間件&#xff0c;數據庫&#xff09; 2、一個系統&#xff0c;背后可能有多個軟/硬件組合支撐&#xff0c;影響性能的因…

互聯網時代如何保證數字足跡的安全,以防個人信息泄露?

用戶在網絡上所做的幾乎所有事情&#xff0c;包括瀏覽、社交媒體活動、搜索查詢、在線訂閱&#xff0c;甚至購物&#xff0c;都會留下一條數據線索&#xff0c;這些數據可用于創建用戶在線身份的詳細檔案。如果這些信息暴露&#xff0c;惡意行為者可能會利用它們將用戶置于各種…

C# IEquatable<T> 使用詳解

總目錄 前言 在 C# 開發中&#xff0c;IEquatable<T> 是一個泛型接口&#xff0c;用于定義類型的相等性比較邏輯。通過實現 IEquatable<T>&#xff0c;可以為自定義類型提供高效的、類型安全的相等性比較方法。本文將詳細介紹 IEquatable<T> 的使用方法、應…

web第四天

Dom操作元素 innerText、innerHTML、value(input and textarea用到) 更改屬性&#xff0c;樣式 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wid…

LabVIEW基于IMAQ實現直線邊緣檢測

本程序基于 NI Vision Development 模塊&#xff0c;通過 IMAQ Find Straight Edges 函數&#xff0c;在指定 ROI&#xff08;感興趣區域&#xff09; 內檢測多條直線邊緣。用戶可 動態調整檢測參數 或 自定義ROI&#xff0c;實時觀察識別效果&#xff0c;適用于 高精度視覺檢測…

費曼學習法13 - 數據表格的魔法:Python Pandas DataFrame 詳解 (Pandas 基礎篇)

第二篇&#xff1a;數據表格的魔法&#xff1a;Python Pandas DataFrame 詳解 (Pandas 基礎篇) 開篇提問&#xff1a; 回憶一下&#xff0c;我們上一篇文章學習了 Pandas 的一維數據結構 Series&#xff0c;它可以看作是帶 “標簽” 的列表。 但現實世界中的數據&#xff0c;…

一周學會Flask3 Python Web開發-在模板中渲染WTForms表單視圖函數里獲取表單數據

鋒哥原創的Flask3 Python Web開發 Flask3視頻教程&#xff1a; 2025版 Flask3 Python web開發 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili 為了能夠在模板中渲染表單&#xff0c;我們需要把表單類實例傳入模板。首先在視圖函數里實例化表單類LoginForm&#xff0c;然…