? ? ? ?本項目將基于PyTorch平臺遷移ResNet18模型。該模型原采用ImageNet數據集(含1000個圖像類別)進行訓練。我們將嘗試運用該模型對螞蟻和蜜蜂進行分類(這兩個類別未包含在原訓練數據集中)。
? ? ? ?本文的原始代碼參考于博客深度學習入門項目——附代碼(持續更新) - 知乎,但是這位博主只給出了代碼,而沒有對代碼進行一些必要的注釋,這對剛入門的菜鳥新手來說不太友好,所以在這里,我對該代碼做了一些詳細的注釋,希望能夠幫助到和我一樣新入門的菜鳥。也同時感謝原作者對代碼整理所付出的勞動!
#加載所需要的庫
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torch.optim.lr_scheduler import StepLRimport torchvision
from torchvision import datasets
from torchvision import models
from torchvision import transformsimport numpy as npfrom io import BytesIO
from urllib.request import urlopen
from zipfile import ZipFileimport matplotlib.pyplot as plt#調用urllib.request.urlopen從下載網址https://pytorch.tips/bee-zip中下載數據,并生成對象#zipresp;
#用IO流來進行操作,無需將zip文件下載到本地磁盤上
zipurl='https://pytorch.tips/bee-zip'
with urlopen(zipurl) as zipresp:with ZipFile(BytesIO(zipresp.read())) as zfile:zfile.extractall('./data')#定義訓練集的變換
train_transforms = transforms.Compose([ #transforms.Compose 是一個工具,用于將多個預
#處理操作組合成一個完整的流程。它接受一個列表,列表中的每個元素是一個預處理操作。# 隨機裁剪并調整大小到 224x224,比如原圖像大小為512x512,是一個隨機操作,每次裁剪的區域可
#能不同,裁剪出224x224transforms.RandomResizedCrop(224), # 用于訓練集,增加數據的多樣性,幫助模型學習
#到更多的特征。# 隨機水平翻轉,概率為 0.5transforms.RandomHorizontalFlip(), #它通過0.5的概率隨機水平翻轉圖像,增加了數據的多樣性,有助于提高模型的泛化能力和減少過擬合。一張貓的圖像無論向左看還是向右看,都是#貓通過隨機水平翻轉,模型可以學習到更多樣的圖像特征。# 將圖片轉換為張量transforms.ToTensor(),# 標準化處理,使用預定義的均值和標準差transforms.Normalize(mean=[0.485, 0.456, 0.406], # ImageNet RGB三個通道的均值std=[0.229, 0.224, 0.225] # ImageNet RGB三個通道的標準差)])
#定義驗證集的變換
val_transforms = transforms.Compose([# 將圖片調整為 256x256transforms.Resize(256), #是一個確定性操作,每次調整大小的結果是相同的。這
#確保了驗證集的圖像在每次運行時都保持一致,從而保證驗證結果的穩定性和可重復性。# 從中心裁剪出 224x224 的區域transforms.CenterCrop(224), #這種組合操作確保了驗證集的圖像在每次運行時都保持一致,同
#時也能保證輸入到模型的圖像大小一致。# 將圖片轉換為張量transforms.ToTensor(), #將圖像從 PIL 圖像或 NumPy 數組轉換為 PyTorch 張量。轉換后的張
#量形狀為 (C, H, W),其中 C 是通道數,H 是高度,W 是寬度。像素值會被歸一化到 [0, 1] 范圍。# 標準化處理,使用預定義的均值和標準差transforms.Normalize(mean=[0.485, 0.456, 0.406], # ImageNet RGB三個通道的均值std=[0.229, 0.224, 0.225] # ImageNet RGB三個通道的標準差)])
#加載數據
#數據集
train_dataset = datasets.ImageFolder(root = './data/hymenoptera_data/train',transform = train_transforms)val_dataset = datasets.ImageFolder(root = './data/hymenoptera_data/val',transform = val_transforms)#數據加載器
train_loader = DataLoader(train_dataset,batch_size=4, #每個批次加載 4 個樣本。這意味著每次迭代會返回 4 ##個樣本及其對應的標簽shuffle=True, #每個 epoch 開始時隨機打亂數據。雖然驗證集通常不需要打亂,但在某些情況下#打亂數據可以避免驗證結果的偏差。num_workers=4 #如果只用一個進程加載數據,GPU每次只能處理一張圖像,處
#理完一張后再處理下一張。而使用 4 個子進程時,可以同時處理 4 張圖像,這樣可以顯著減少數據加#載的#總時間。
)
val_loader = DataLoader(val_dataset,batch_size=4,shuffle=True,num_workers=4
)#生成ResNet18模型
#模型
model = models.resnet18(pretrained = True)#這是 PyTorch 提供的預定義 ResNet18 模型。pretrained=True:表示加載預訓練的權重。這些權重是在 ImageNet 數據集上訓練得到的,通常 #用于遷#移學習。
print(model.fc) #fc 是 ResNet18 模型中#的最后一個全連接層。默認情況下,ResNet18 的全連接層有 1000 個輸出節點,對應于 ImageNet 數據
#集的 1000 個類別。
model.fc = nn.Linear(model.fc.in_features, 2) #model.fc.in_features:獲取原全連接層的輸入特#征數量。ResNet18 的全連接層輸入特征數量為 512。nn.Linear(model.fc.in_features, 2):創建一
#個新的全連接層,輸入特征數量保持不變,輸出特征數量改為 2。這通常用于二分類任務。
print(model.fc)#定義超參數
model = model.to("cuda")#將模型的所有參數和緩沖區移動到 GPU 上。這使得模型可以在 GPU 上進行訓#練,從而顯著提高訓練速度。Loss = nn.CrossEntropyLoss()#這是 PyTorch 提供的交叉熵損失函數,通常用于多分類任務。它結合了 LogSoftmax 和 NLLLoss,適用于分類任務。optim = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)# optim.SGD:這是隨機梯度下降(SGD)優化器。model.parameters():獲取模型的所有可訓練參#數。model.parameters():lr=0.001:設置學習率為 0.001。momentum=0.9:設置動量為 0.9,動量可#以幫助優化器更快地收斂,并減少振蕩。exp_lr_scheduler = StepLR(optim, step_size=7, gamma=0.1)#StepLR:這是 PyTorch 提供的學習率#調度器,用于在訓練過程中調整學習率。step_size=7:每 7 個 epoch,學習率會調整一次。
#gamma=0.1:每次調整學習率時,學習率會乘以 0.1。例如,如果初始學習率為 0.001,那么在第 7 個 #epoch 時,學習率會變為 0.0001。#可微調
#訓練
num_epochs = 25 #定義了訓練的總輪數,即模型將完整地遍歷訓練數據集的次數。在這里,設置為 25輪。
for epoch in range(num_epochs):#訓練模型model.train() #將模型設置為訓練模式。這會影響某些層的
#行為,如 Dropout 和 BatchNorm,確保它們在訓練時的行為與評估時不同。running_loss = 0.0 #初始化running_loss:用于累計每個 epoch 的總#損失。running_corrects = 0 #初始化running_corrects:用于累計每個 epoch 中
#正確預測的數量。for inputs, labels in train_loader: #train_loader:數據加載器,每次迭代返回一個批次的數
#據和標簽。inputs = inputs.to("cuda") #將輸入數據移動到 GPU 上。labels = labels.to("cuda") #將標簽數據移動到 GPU 上。outputs = model(inputs) #將輸入數據通過模型進行前向傳播,得到模型的輸出。_, preds = torch.max(outputs, 1) #獲取模型輸出的最大值及其索引。preds 是預測的類別索#引。loss = Loss(outputs, labels) #計算模型輸出和真實標簽之間的損失值loss.backward() #反向傳播,計算損失值的梯度,并將其傳播回#模型的每個參數。optim.step() #根據計算得到的梯度更新模型的參數。optim.zero_grad() #清空之前的梯度,避免梯度累積# 累計損失和正確預測數running_loss += loss.item()/inputs.size(0) #獲取損失值的標量值。running_corrects += torch.sum(preds == labels.data)/inputs.size(0) #torch.sum(preds == labels.data):計算預測正確的數量。inputs.size(0):獲取當前批次的樣本數量。exp_lr_scheduler.step() #根據學習率調度器的設置更新學習率。train_epoch_loss = running_loss/len(train_loader) #計算每個 epoch 的平均損失。train_epoch_acc = running_corrects/len(train_loader)#計算每個 epoch 的平均準確率。#測試模型model.eval()running_loss = 0.0running_corrects = 0for inputs, labels in val_loader:inputs = inputs.to("cuda")labels = labels.to("cuda")outputs = model(inputs)_, preds = torch.max(outputs, 1)loss = Loss(outputs, labels)running_loss += loss.item()/inputs.size(0)running_corrects += torch.sum(preds == labels.data)/inputs.size(0)epoch_loss = running_loss/len(val_loader)epoch_acc = running_corrects/len(val_loader)if((epoch+1)%5==0): #每隔 5 個 epoch,輸出當前的訓練和驗證的損失和準確率。print("epoch:{}, ""Train_loss:{:.4f} Train_acc:{:.4f}, ""Loss:{:.4f}, Acc:{:.4f}".format(epoch+1, train_epoch_loss, train_epoch_acc, epoch_loss, epoch_acc))#測試可視化 這段代碼定義了一個函數 imshow,用于將經過預處理的圖像數據可視化,并顯示其預測的類
#別。它還展示了如何使用這個函數來可視化驗證集中的圖像及其預測結果
def imshow(inp, title=None):#從 C-H-W 切換回 H-W-C 圖像格式inp = inp.numpy().transpose((1, 2, 0)) #將輸入的 PyTorch 張量轉換為 NumPy 數組 從通道#優先格式(C-H-W)轉換為高度-寬度-通道格式(H-W-C),以便使用 matplotlib 進行可視化。#撤銷歸一化mean = np.array([0.485, 0.456, 0.406]) #均值數組,用于撤銷歸一化。std = np.array([0.229, 0.224, 0.225]) #標準差數組,用于撤銷歸一化。inp = std * inp + mean #撤銷歸一化操作,將圖像數據恢復到原始范圍。inp = np.clip(inp, 0, 1) #將圖像數據裁剪到 [0, 1] 范圍內,確保數據有效。plt.imshow(inp) #使用 matplotlib 的 imshow 函數顯示圖像。if title is not None:plt.title(title)inputs , classes = next(iter(val_loader)) #從驗證集加載器中獲取一個批次的數據和標簽。
out = torchvision.utils.make_grid(inputs) #將一個批次的圖像拼接成一個網格,便于可視化
class_names = val_dataset.classes #val_dataset.classes:獲取驗證集中的類別名稱列表
outputs = model(inputs.to("cuda")) #inputs.to("cuda"):將輸入圖像移動到 GPU。model(inputs.to("cuda")):將輸入圖像通過模型進行前向傳播,得到模型的輸出。_, preds = torch.max(outputs, 1) #獲取模型輸出的最大值及其索引,preds 是預測的類別索引。imshow(out, title=[class_names[x] for x in preds]) #根據預測的類別索引獲取對應的類別名稱
????????????????