習(xí)原理到PyTorch實(shí)戰(zhàn)微調(diào))
1. 項(xiàng)目概述為什么ResNet101是繞不開(kāi)的里程碑如果你在圖像識(shí)別領(lǐng)域摸爬滾打過(guò)幾年一定會(huì)對(duì)2015年橫空出世的ResNet殘差網(wǎng)絡(luò)記憶猶新。它不像一些曇花一現(xiàn)的模型ResNet系列尤其是ResNet101幾乎成了過(guò)去近十年深度學(xué)習(xí)在計(jì)算機(jī)視覺(jué)任務(wù)中的“基礎(chǔ)設(shè)施”和“基準(zhǔn)線”。無(wú)論是做學(xué)術(shù)研究發(fā)論文還是在工業(yè)界部署一個(gè)分類(lèi)、檢測(cè)或分割模型ResNet101常常是那個(gè)你第一個(gè)會(huì)想到去嘗試、去對(duì)比、甚至直接拿來(lái)微調(diào)的骨干網(wǎng)絡(luò)。今天我們就拋開(kāi)那些教科書(shū)式的定義從一個(gè)實(shí)踐者的角度深入聊聊ResNet101到底“神”在哪里以及我們?nèi)绾卧谡鎸?shí)的圖像識(shí)別項(xiàng)目中用好它。簡(jiǎn)單來(lái)說(shuō)ResNet101解決了一個(gè)困擾深度學(xué)習(xí)界多年的核心難題網(wǎng)絡(luò)深度增加時(shí)性能不升反降。在它之前大家普遍認(rèn)為網(wǎng)絡(luò)越深學(xué)習(xí)能力越強(qiáng)但實(shí)際搭建超過(guò)20層的網(wǎng)絡(luò)時(shí)準(zhǔn)確率會(huì)飽和甚至下降這不是過(guò)擬合而是網(wǎng)絡(luò)變得難以訓(xùn)練梯度在反向傳播中消失或爆炸。ResNet通過(guò)引入“殘差學(xué)習(xí)”的巧妙思想讓網(wǎng)絡(luò)可以輕松堆疊到上百層乃至上千層從而實(shí)現(xiàn)了性能的突破性提升。ResNet101顧名思義就是一個(gè)擁有101層深度的殘差網(wǎng)絡(luò)它在精度和復(fù)雜度之間取得了極佳的平衡使其成為應(yīng)用最廣泛的版本之一。這篇文章適合所有對(duì)深度學(xué)習(xí)實(shí)戰(zhàn)感興趣的朋友無(wú)論你是剛?cè)腴T(mén)想找一個(gè)經(jīng)典模型練手還是有一定經(jīng)驗(yàn)需要深入理解骨干網(wǎng)絡(luò)的設(shè)計(jì)哲學(xué)。我會(huì)結(jié)合原理、代碼和大量實(shí)戰(zhàn)中的“坑”帶你重新認(rèn)識(shí)這個(gè)巔峰之作。2. 核心思想拆解殘差學(xué)習(xí)為何是“神來(lái)之筆”2.1 深度網(wǎng)絡(luò)的退化問(wèn)題與直覺(jué)突破在ResNet出現(xiàn)之前VGGNet通過(guò)堆疊3x3的小卷積核將網(wǎng)絡(luò)深度推到了19層取得了不錯(cuò)的效果。大家自然想如果能訓(xùn)練一個(gè)更深的VGG比如50層、100層效果應(yīng)該更好。但實(shí)驗(yàn)結(jié)果潑了一盆冷水56層的普通網(wǎng)絡(luò)在訓(xùn)練集和測(cè)試集上的錯(cuò)誤率都比20層的網(wǎng)絡(luò)要高。這明確表明這不是過(guò)擬合過(guò)擬合在訓(xùn)練集上表現(xiàn)好測(cè)試集差而是深度網(wǎng)絡(luò)本身變得難以優(yōu)化。問(wèn)題的根源在于梯度。在反向傳播中梯度需要從損失函數(shù)一層層回傳指導(dǎo)每一層參數(shù)的更新。當(dāng)網(wǎng)絡(luò)非常深時(shí)梯度在連續(xù)相乘鏈?zhǔn)椒▌t的過(guò)程中如果大部分乘數(shù)小于1梯度會(huì)指數(shù)級(jí)衰減到近乎為零梯度消失如果大部分乘數(shù)大于1則會(huì)指數(shù)級(jí)爆炸梯度爆炸。雖然通過(guò)精心初始化如He初始化和批量歸一化BatchNorm可以緩解爆炸問(wèn)題但消失問(wèn)題在極深網(wǎng)絡(luò)中依然棘手。ResNet的作者何愷明等人提出了一個(gè)反直覺(jué)卻極其優(yōu)雅的解決方案既然深層網(wǎng)絡(luò)難以學(xué)習(xí)一個(gè)恒等映射即輸出等于輸入那我們就不讓它直接學(xué)習(xí)目標(biāo)映射H(x)而是讓它學(xué)習(xí)目標(biāo)映射與輸入x之間的殘差F(x) H(x) - x。這樣原始的目標(biāo)映射實(shí)際上變成了 H(x) F(x) x。這個(gè)改動(dòng)看似微小卻帶來(lái)了革命性的變化。如果某一層的F(x)學(xué)習(xí)效果不佳或者我們甚至認(rèn)為這一層是多余的那么最理想的情況就是讓F(x) 0。在殘差塊中讓F(x)0遠(yuǎn)比在一個(gè)普通卷積塊中讓H(x)x要容易得多。因?yàn)閷?duì)于普通卷積層讓H(x)x意味著需要讓權(quán)重矩陣學(xué)習(xí)成一個(gè)單位矩陣這是有難度的而對(duì)于殘差塊只需將其權(quán)重推向零即可。這為網(wǎng)絡(luò)提供了一條“捷徑”Shortcut Connection讓梯度可以幾乎無(wú)損地直接流過(guò)極大地緩解了梯度消失問(wèn)題使得訓(xùn)練成百上千層的網(wǎng)絡(luò)成為可能。2.2 殘差塊的結(jié)構(gòu)與兩種Identity Mapping理解了思想我們來(lái)看具體實(shí)現(xiàn)。ResNet的基礎(chǔ)構(gòu)件是“殘差塊”。以最經(jīng)典的兩種為例BasicBlock用于較淺的ResNet如18、34層它包含兩個(gè)3x3卷積層 shortcut connection直接將輸入x加到第二個(gè)卷積層的輸出上。公式為y F(x, {Wi}) x。這里的加法要求F(x)的維度必須與x完全相同。在ResNet-34中所有塊的輸入輸出通道數(shù)一致所以可以直接相加。Bottleneck Block用于更深的ResNet如50、101、152層這是ResNet101使用的塊。為了在加深網(wǎng)絡(luò)的同時(shí)控制計(jì)算量它采用了“瓶頸”結(jié)構(gòu)先是一個(gè)1x1卷積降維減少通道數(shù)例如降到1/4然后是一個(gè)3x3卷積進(jìn)行特征提取最后再用一個(gè)1x1卷積升維回原始通道數(shù)。這樣3x3卷積處理的是低維特征大大減少了參數(shù)量和計(jì)算量。其公式同樣是 y F(x, {Wi}) x。這里有一個(gè)關(guān)鍵細(xì)節(jié)當(dāng)殘差塊需要改變特征圖的尺寸下采樣或通道數(shù)時(shí)shortcut connection就不能直接相加了因?yàn)榫S度不匹配。ResNet的解決方案是在shortcut路徑上增加一個(gè)1x1卷積層配合步幅2進(jìn)行下采樣用來(lái)調(diào)整維度和尺寸使其能與主路徑的輸出相加。這個(gè)1x1卷積層通常被稱為“投影捷徑”。注意在實(shí)際編程中如PyTorch官方實(shí)現(xiàn)這個(gè)投影捷徑的1x1卷積通常不包含偏置項(xiàng)biasFalse。這是因?yàn)榫o接著的相加操作后會(huì)有一個(gè)BatchNorm層BN層本身有可學(xué)習(xí)的縮放和平移參數(shù)足以替代偏置的作用。添加額外的偏置可能導(dǎo)致訓(xùn)練不穩(wěn)定這是一個(gè)容易被忽略但重要的工程細(xì)節(jié)。3. ResNet101網(wǎng)絡(luò)架構(gòu)全景與核心參數(shù)解析3.1 層數(shù)計(jì)算與結(jié)構(gòu)總覽ResNet101的名字容易讓人誤解為正好101層卷積。實(shí)際上這里的“101層”指的是帶權(quán)重的層數(shù)主要包括卷積層和全連接層。我們以最常用的配置來(lái)拆解初始層一個(gè)7x7卷積stride2 一個(gè)3x3最大池化stride2。這算作1個(gè)卷積層。四個(gè)階段StageStage1: 使用Bottleneck Block重復(fù)[3]次。每個(gè)Bottleneck有3個(gè)卷積層共 3 * 3 9層。Stage2: 使用Bottleneck Block重復(fù)[4]次。共 4 * 3 12層。Stage3: 使用Bottleneck Block重復(fù)[23]次。共 23 * 3 69層。這是ResNet101深度的大頭Stage4: 使用Bottleneck Block重復(fù)[3]次。共 3 * 3 9層。末尾層全局平均池化 一個(gè)全連接層1000個(gè)神經(jīng)元對(duì)應(yīng)ImageNet的1000類(lèi)。全連接層算1層。總層數(shù)計(jì)算1初始卷積 9 12 69 9 1全連接 101層。這正是其名稱的由來(lái)。每個(gè)Stage的第一個(gè)Bottleneck Block通常會(huì)進(jìn)行下采樣通過(guò)stride2從而將特征圖尺寸減半同時(shí)通道數(shù)翻倍具體翻倍規(guī)則由Bottleneck的第一個(gè)1x1卷積決定。3.2 特征圖尺寸與通道數(shù)變化流程假設(shè)輸入圖像為224x224x3RGB三通道我們跟蹤數(shù)據(jù)在ResNet101中的旅程Conv1 (7x7, stride2, padding3)輸出尺寸 (224-72*3)/2 1 112。輸出通道64。尺寸112x112x64。MaxPool (3x3, stride2)輸出尺寸 (112-3)/2 1 56。尺寸56x56x64。Stage1 (Conv2_x)3個(gè)Bottleneck。輸入輸出通道數(shù)均為256注意Bottleneck內(nèi)部先降到64再升回256。注意Stage1內(nèi)部不進(jìn)行空間下采樣所以最終輸出尺寸仍是56x56但通道數(shù)從64提升到了256。尺寸56x56x256。Stage2 (Conv3_x)4個(gè)Bottleneck。第一個(gè)Block進(jìn)行下采樣stride2將尺寸減半為28x28同時(shí)輸出通道數(shù)提升到512。尺寸28x28x512。Stage3 (Conv4_x)23個(gè)Bottleneck。第一個(gè)Block下采樣尺寸減半為14x14輸出通道數(shù)提升到1024。尺寸14x14x1024。Stage4 (Conv5_x)3個(gè)Bottleneck。第一個(gè)Block下采樣尺寸減半為7x7輸出通道數(shù)提升到2048。尺寸7x7x2048。全局平均池化 (Global Average Pooling)將7x7的特征圖每個(gè)通道取平均值得到一個(gè)2048維的向量。尺寸1x1x2048。全連接層 (FC)將2048維向量映射到1000維ImageNet類(lèi)別數(shù)。尺寸1000。這個(gè)從高分辨率、低語(yǔ)義信息到低分辨率、高語(yǔ)義信息的特征提取過(guò)程是卷積神經(jīng)網(wǎng)絡(luò)的經(jīng)典范式。ResNet101的深度保證了其在各個(gè)尺度上都能學(xué)習(xí)到豐富的特征。4. 實(shí)戰(zhàn)在自定義數(shù)據(jù)集上微調(diào)ResNet101理論再精彩不如動(dòng)手一試。現(xiàn)在我們假設(shè)你手頭有一個(gè)自己的圖像分類(lèi)數(shù)據(jù)集比如分辨10種不同的花卉來(lái)演示如何利用PyTorch快速微調(diào)一個(gè)預(yù)訓(xùn)練的ResNet101模型。微調(diào)是應(yīng)用深度學(xué)習(xí)最實(shí)用、最高效的手段之一。4.1 環(huán)境準(zhǔn)備與數(shù)據(jù)組織首先確保你的環(huán)境已安裝PyTorch和TorchVision。數(shù)據(jù)組織我強(qiáng)烈推薦遵循ImageFolder的格式這是最省心的方式y(tǒng)our_dataset/ ├── train/ │ ├── class1/ │ │ ├── img1.jpg │ │ └── img2.jpg │ ├── class2/ │ │ ├── img3.jpg │ │ └── ... │ └── ... └── val/ ├── class1/ ├── class2/ └── ...train和val分別代表訓(xùn)練集和驗(yàn)證集每個(gè)子文件夾的名字就是類(lèi)別標(biāo)簽。這樣PyTorch的ImageFolder加載器會(huì)自動(dòng)處理好標(biāo)簽映射。4.2 模型加載與改造我們使用torchvision.models中提供的預(yù)訓(xùn)練模型。預(yù)訓(xùn)練權(quán)重是在ImageNet上訓(xùn)練的包含了豐富的通用視覺(jué)特征。import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision.datasets as datasets from torchvision import models # 1. 加載預(yù)訓(xùn)練模型并獲取其輸出層的輸入特征數(shù) model models.resnet101(weightsmodels.ResNet101_Weights.IMAGENET1K_V2) # 使用V2權(quán)重效果更好 num_ftrs model.fc.in_features # 獲取全連接層輸入特征數(shù)對(duì)于ResNet101是2048 # 2. 關(guān)鍵步驟替換最后的全連接層fc # 假設(shè)我們的自定義數(shù)據(jù)集有10個(gè)類(lèi)別 num_classes 10 model.fc nn.Linear(num_ftrs, num_classes) # 3. 將模型轉(zhuǎn)移到GPU如果可用 device torch.device(cuda:0 if torch.cuda.is_available() else cpu) model model.to(device)這里有一個(gè)非常重要的技巧通常我們不僅僅替換最后一層。對(duì)于卷積部分通常稱為“骨干網(wǎng)絡(luò)”或“特征提取器”在訓(xùn)練初期我們希望保留其已經(jīng)學(xué)到的強(qiáng)大特征只讓新?lián)Q上的全連接層快速學(xué)習(xí)。因此我們會(huì)凍結(jié)骨干網(wǎng)絡(luò)的參數(shù)。# 凍結(jié)除最后一層fc外的所有參數(shù) for name, param in model.named_parameters(): if fc not in name: # 只訓(xùn)練fc層 param.requires_grad False # 也可以選擇性地解凍后面幾個(gè)階段如Stage4進(jìn)行精細(xì)調(diào)優(yōu) # for name, param in model.named_parameters(): # if layer4 in name or fc in name: # 解凍Stage4和fc層 # param.requires_grad True # else: # param.requires_grad False凍結(jié)操作通過(guò)設(shè)置requires_grad False實(shí)現(xiàn)這樣在反向傳播時(shí)這些參數(shù)就不會(huì)被更新大大減少了訓(xùn)練初期的計(jì)算量和內(nèi)存占用并有助于防止小數(shù)據(jù)集上的過(guò)擬合。4.3 數(shù)據(jù)預(yù)處理、加載與訓(xùn)練策略數(shù)據(jù)預(yù)處理需要和ImageNet預(yù)訓(xùn)練時(shí)保持一致因?yàn)轭A(yù)訓(xùn)練權(quán)重是在特定數(shù)據(jù)分布上學(xué)習(xí)的。TorchVision提供了非常方便的轉(zhuǎn)換組合。# 定義訓(xùn)練和驗(yàn)證的數(shù)據(jù)增強(qiáng)與預(yù)處理 # IMAGENET的均值和標(biāo)準(zhǔn)差 mean [0.485, 0.456, 0.406] std [0.229, 0.224, 0.225] train_transform transforms.Compose([ transforms.RandomResizedCrop(224), # 隨機(jī)裁剪并縮放到224x224 transforms.RandomHorizontalFlip(), # 隨機(jī)水平翻轉(zhuǎn)簡(jiǎn)單有效的增強(qiáng) transforms.ToTensor(), # 轉(zhuǎn)為T(mén)ensor并歸一化到[0,1] transforms.Normalize(mean, std) # 用ImageNet統(tǒng)計(jì)值標(biāo)準(zhǔn)化 ]) val_transform transforms.Compose([ transforms.Resize(256), # 將短邊縮放到256 transforms.CenterCrop(224), # 中心裁剪224x224 transforms.ToTensor(), transforms.Normalize(mean, std) ]) # 加載數(shù)據(jù)集 train_dataset datasets.ImageFolder(rootpath/to/your_dataset/train, transformtrain_transform) val_dataset datasets.ImageFolder(rootpath/to/your_dataset/val, transformval_transform) train_loader torch.utils.data.DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader torch.utils.data.DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4)接下來(lái)是訓(xùn)練循環(huán)的核心部分。由于我們凍結(jié)了大部分參數(shù)優(yōu)化器只優(yōu)化那些需要梯度的參數(shù)。import torch.optim as optim from torch.optim import lr_scheduler # 定義損失函數(shù)和優(yōu)化器只優(yōu)化需要梯度的參數(shù) criterion nn.CrossEntropyLoss() optimizer optim.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr0.001, momentum0.9) # 使用學(xué)習(xí)率調(diào)度器在訓(xùn)練過(guò)程中降低學(xué)習(xí)率有助于收斂 scheduler lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) # 每7個(gè)epoch學(xué)習(xí)率乘以0.1 num_epochs 25 for epoch in range(num_epochs): # 訓(xùn)練階段 model.train() running_loss 0.0 for inputs, labels in train_loader: inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) epoch_loss running_loss / len(train_dataset) scheduler.step() # 更新學(xué)習(xí)率 # 驗(yàn)證階段 model.eval() correct 0 total 0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels inputs.to(device), labels.to(device) outputs model(inputs) _, predicted torch.max(outputs.data, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc 100 * correct / total print(fEpoch {epoch1}/{num_epochs}, Loss: {epoch_loss:.4f}, Val Acc: {val_acc:.2f}%)這個(gè)流程是一個(gè)基礎(chǔ)但完整的微調(diào)范例。在實(shí)際項(xiàng)目中你可能還需要加入TensorBoard或WandB進(jìn)行可視化監(jiān)控使用早停Early Stopping策略防止過(guò)擬合以及嘗試不同的優(yōu)化器如AdamW和學(xué)習(xí)率預(yù)熱Warmup策略。5. 超越分類(lèi)ResNet101作為強(qiáng)大的特征提取器ResNet101的價(jià)值遠(yuǎn)不止于圖像分類(lèi)。其強(qiáng)大的特征提取能力使其成為眾多計(jì)算機(jī)視覺(jué)高級(jí)任務(wù)的基石“骨干網(wǎng)絡(luò)”。預(yù)訓(xùn)練的ResNet101提供了一個(gè)非常好的特征起點(diǎn)我們可以在其基礎(chǔ)上“嫁接”不同的任務(wù)頭。5.1 目標(biāo)檢測(cè)Faster R-CNN與Mask R-CNN在目標(biāo)檢測(cè)領(lǐng)域Faster R-CNN是一個(gè)里程碑式的兩階段檢測(cè)器。它的核心組成部分之一就是區(qū)域提議網(wǎng)絡(luò)和區(qū)域興趣池化之后的特征提取。通常我們會(huì)用一個(gè)預(yù)訓(xùn)練的ResNet101去掉最后的全連接層和全局平均池化作為骨干網(wǎng)絡(luò)接上一個(gè)特征金字塔網(wǎng)絡(luò)為RPN和檢測(cè)頭提供多尺度的特征圖。ResNet的深層特征如Stage4的輸出包含豐富的語(yǔ)義信息利于分類(lèi)而淺層特征如Stage2、3的輸出包含更多的細(xì)節(jié)和位置信息利于定位。這種結(jié)合使得檢測(cè)精度大幅提升。Mask R-CNN更進(jìn)一步在Faster R-CNN的基礎(chǔ)上增加了一個(gè)并行的分支用于預(yù)測(cè)每個(gè)目標(biāo)實(shí)例的像素級(jí)分割掩碼。ResNet101同樣是其最常用的骨干網(wǎng)絡(luò)之一為實(shí)例分割任務(wù)提供了堅(jiān)實(shí)的特征基礎(chǔ)。5.2 語(yǔ)義分割FCN與U-Net變體全卷積網(wǎng)絡(luò)是語(yǔ)義分割的開(kāi)山之作。它的思想是將傳統(tǒng)CNN最后的全連接層替換為卷積層使網(wǎng)絡(luò)可以接受任意尺寸的輸入并輸出相同空間分辨率的預(yù)測(cè)圖。一個(gè)典型的做法是將預(yù)訓(xùn)練的ResNet101作為編碼器將其最后三個(gè)階段Stage2,3,4的輸出通過(guò)反卷積層或上采樣操作與更淺層的特征進(jìn)行融合跳躍連接逐步恢復(fù)空間細(xì)節(jié)最終得到像素級(jí)的分類(lèi)結(jié)果。ResNet101的深度保證了編碼器能提取到高級(jí)語(yǔ)義特征而跳躍連接則彌補(bǔ)了空間信息的損失。5.3 關(guān)鍵點(diǎn)檢測(cè)與姿態(tài)估計(jì)在人體姿態(tài)估計(jì)任務(wù)中我們需要定位人體關(guān)節(jié)點(diǎn)的坐標(biāo)。一種主流方法是采用“熱圖回歸”。網(wǎng)絡(luò)以整張圖片為輸入為每個(gè)關(guān)節(jié)點(diǎn)輸出一張概率熱圖峰值點(diǎn)即為預(yù)測(cè)的關(guān)節(jié)點(diǎn)位置。這類(lèi)網(wǎng)絡(luò)如SimpleBaseline、HRNet的骨干部分也廣泛采用ResNet101。深層網(wǎng)絡(luò)學(xué)習(xí)到的關(guān)于人體結(jié)構(gòu)、部件關(guān)系的先驗(yàn)知識(shí)對(duì)于準(zhǔn)確推斷被遮擋或模糊的關(guān)節(jié)點(diǎn)至關(guān)重要。6. 工程化考量部署優(yōu)化與常見(jiàn)陷阱當(dāng)你訓(xùn)練好一個(gè)基于ResNet101的模型并準(zhǔn)備投入實(shí)際應(yīng)用時(shí)會(huì)面臨一系列工程挑戰(zhàn)。6.1 模型壓縮與加速ResNet101有約4450萬(wàn)個(gè)參數(shù)前向推理一次需要約7.6 GFLOPs的計(jì)算量。在資源受限的邊緣設(shè)備如手機(jī)、嵌入式設(shè)備上直接運(yùn)行是不現(xiàn)實(shí)的。因此模型壓縮與加速是必經(jīng)之路。剪枝識(shí)別并移除網(wǎng)絡(luò)中不重要的連接或通道。例如可以通過(guò)衡量卷積核的L1范數(shù)將范數(shù)小的通道剪掉然后對(duì)剪枝后的網(wǎng)絡(luò)進(jìn)行微調(diào)以恢復(fù)精度。量化將模型權(quán)重和激活從32位浮點(diǎn)數(shù)轉(zhuǎn)換為低精度格式如8位整數(shù)。這能顯著減少模型大小和內(nèi)存帶寬需求并利用硬件對(duì)整型運(yùn)算的加速能力。PyTorch和TensorFlow都提供了成熟的量化工具。知識(shí)蒸餾用一個(gè)龐大而精確的教師模型如ResNet101去指導(dǎo)一個(gè)小而快的學(xué)生模型如MobileNetV3的訓(xùn)練讓學(xué)生模型模仿教師模型的行為從而獲得接近大模型的性能。使用更高效的架構(gòu)如果對(duì)延遲極其敏感可以考慮直接使用為移動(dòng)端設(shè)計(jì)的網(wǎng)絡(luò)如EfficientNet、MobileNet系列或者在ResNet基礎(chǔ)上改進(jìn)的RegNet、ResNeSt等。6.2 實(shí)際部署中的輸入處理訓(xùn)練時(shí)我們使用了一整套預(yù)處理流程隨機(jī)裁剪、翻轉(zhuǎn)等。但在部署推理時(shí)必須保證預(yù)處理與訓(xùn)練時(shí)完全一致除了數(shù)據(jù)增強(qiáng)部分。通常推理時(shí)只保留Resize、CenterCrop、ToTensor和Normalize。一個(gè)常見(jiàn)的錯(cuò)誤是忘記做歸一化或者使用了錯(cuò)誤的均值和標(biāo)準(zhǔn)差這會(huì)導(dǎo)致模型性能?chē)?yán)重下降。另外輸入圖像的尺寸不一定非要固定為224x224。全卷積網(wǎng)絡(luò)可以處理任意尺寸但ResNet101的全局平均池化層要求輸入在進(jìn)入該層之前特征圖尺寸必須被下采樣到7x7。如果你改變了輸入尺寸需要確保網(wǎng)絡(luò)的總下采樣倍數(shù)通常是32倍能整除你的輸入尺寸否則特征圖尺寸會(huì)出現(xiàn)小數(shù)導(dǎo)致錯(cuò)誤。例如輸入320x320經(jīng)過(guò)5次stride2的下采樣得到10x10的特征圖無(wú)法進(jìn)行7x7的全局平均池化。這時(shí)你可能需要將全局平均池化層替換為自適應(yīng)平均池化層nn.AdaptiveAvgPool2d((1, 1))它可以接受任意尺寸的輸入并輸出1x1的特征圖。6.3 避坑指南與經(jīng)驗(yàn)分享預(yù)訓(xùn)練權(quán)重版本torchvision.models提供的預(yù)訓(xùn)練權(quán)重有多個(gè)版本如IMAGENET1K_V1,IMAGENET1K_V2。V2版本通常使用了更好的訓(xùn)練技巧如標(biāo)簽平滑、不同的數(shù)據(jù)增強(qiáng)效果比V1有提升。在加載時(shí)務(wù)必指定正確的權(quán)重并確保預(yù)處理與權(quán)重版本匹配。BatchNorm層在微調(diào)時(shí)的狀態(tài)BatchNorm層在訓(xùn)練和評(píng)估推理時(shí)的行為不同它依賴于批次的統(tǒng)計(jì)信息。當(dāng)你凍結(jié)了骨干網(wǎng)絡(luò)進(jìn)行微調(diào)時(shí)BatchNorm層默認(rèn)處于訓(xùn)練模式會(huì)繼續(xù)更新其running_mean和running_var。對(duì)于小數(shù)據(jù)集這可能不穩(wěn)定。一種實(shí)踐是也將這些BatchNorm層設(shè)置為評(píng)估模式model.eval()或者使用torch.no_grad()上下文管理器。更精細(xì)的控制可以通過(guò)設(shè)置model.train()和model.eval()或者手動(dòng)設(shè)置bn_layer.training False。學(xué)習(xí)率設(shè)置對(duì)于微調(diào)新添加的層如替換的fc層需要較大的學(xué)習(xí)率來(lái)快速學(xué)習(xí)而已凍結(jié)或部分凍結(jié)的預(yù)訓(xùn)練層則需要非常小的學(xué)習(xí)率進(jìn)行精細(xì)調(diào)整。因此使用分組學(xué)習(xí)率策略param_groups通常是更好的選擇為不同部分設(shè)置不同的學(xué)習(xí)率。內(nèi)存溢出OOMResNet101的深度和寬度使其對(duì)顯存需求較高。如果遇到OOM錯(cuò)誤首先嘗試減小batch_size。如果必須使用大batch可以考慮使用梯度累積多次前向傳播累積梯度再一次性反向更新模擬大batch的效果。另外使用混合精度訓(xùn)練torch.cuda.amp也能有效降低顯存占用并加速訓(xùn)練。類(lèi)別不平衡問(wèn)題如果你的自定義數(shù)據(jù)集類(lèi)別嚴(yán)重不平衡使用標(biāo)準(zhǔn)的交叉熵?fù)p失會(huì)導(dǎo)致模型偏向多數(shù)類(lèi)。可以嘗試使用帶權(quán)重的交叉熵?fù)p失nn.CrossEntropyLoss(weightclass_weights)其中class_weights可以根據(jù)每個(gè)類(lèi)別的樣本數(shù)倒數(shù)或其他方法計(jì)算。或者使用Focal Loss它通過(guò)降低易分類(lèi)樣本的權(quán)重使模型更關(guān)注難分類(lèi)和稀有的樣本。