習(xí)的農(nóng)作物病蟲害識(shí)別系統(tǒng):從數(shù)據(jù)集到部署的完整實(shí)戰(zhàn)指南)
簡(jiǎn)介這是一套面向高校計(jì)算機(jī)、人工智能與智慧農(nóng)業(yè)方向?qū)W生的畢業(yè)設(shè)計(jì)完整資料圍繞深度學(xué)習(xí)在常見農(nóng)作物病蟲害識(shí)別中的應(yīng)用展開適合需要完成高分畢設(shè)或系統(tǒng)學(xué)習(xí)圖像分類實(shí)戰(zhàn)的讀者。壓縮包共281個(gè)文件約522.63MB包含14個(gè)Python源碼文件、8個(gè)Vue前端文件與7個(gè)JavaScript腳本構(gòu)成可運(yùn)行的識(shí)別系統(tǒng)另有107個(gè)txt說明、83張jpg與34張png圖像樣本、6份pdf及3份caj參考文獻(xiàn)、3份docx論文文檔覆蓋數(shù)據(jù)、代碼與寫作全流程。資源已獲導(dǎo)師指導(dǎo)并通過內(nèi)含論文各章節(jié)內(nèi)容從緒論、國(guó)內(nèi)外研究現(xiàn)狀、數(shù)據(jù)收集與視覺顯著性處理到卷積神經(jīng)網(wǎng)絡(luò)原理、Inception-V3與MobileNet-V2模型對(duì)比均有涉及可幫助讀者理解從數(shù)據(jù)獲取、負(fù)樣本構(gòu)建到模型訓(xùn)練與部署的完整鏈路。目前已有2897人學(xué)習(xí)下載適合作為畢設(shè)參考與深度學(xué)習(xí)項(xiàng)目練手素材。1. 從一張黃瓜葉片的照片說起這套病蟲害識(shí)別系統(tǒng)到底能做什么去年夏天一個(gè)做溫室種植的朋友發(fā)來一張照片黃瓜葉片上布滿黃褐色斑點(diǎn)邊緣還帶著一圈黃暈。他問我這是什么病我盯著看了半天只敢說“可能是霜霉病也可能是靶斑病”因?yàn)檫@兩種病害在早期葉片上的表現(xiàn)實(shí)在太像了。后來把照片發(fā)給農(nóng)科院的朋友人家用顯微鏡一看確診是靶斑病。這件事讓我意識(shí)到農(nóng)作物病蟲害識(shí)別這件事靠人眼和經(jīng)驗(yàn)在常見病害上還能湊合一旦遇到相似癥狀或者早期感染翻車概率非常高?;谏疃葘W(xué)習(xí)的常見農(nóng)作物病蟲害識(shí)別系統(tǒng)要解決的就是這個(gè)問題給一張葉片或者植株的照片模型告訴你這大概率是什么病、置信度多少、建議怎么處理。它適合三類人一是做畢業(yè)設(shè)計(jì)的學(xué)生需要一個(gè)有完整源碼、教程和論文框架的課題二是想入門深度學(xué)習(xí)但不知道拿什么數(shù)據(jù)集練手的開發(fā)者農(nóng)業(yè)圖像分類是一個(gè)數(shù)據(jù)相對(duì)干凈、類別明確、落地場(chǎng)景清晰的方向三是做智慧農(nóng)業(yè)相關(guān)產(chǎn)品的工程師需要快速驗(yàn)證一個(gè)識(shí)別模塊能不能嵌入到現(xiàn)有的巡檢或者監(jiān)測(cè)流程里。這套系統(tǒng)的核心鏈路其實(shí)不復(fù)雜采集圖像、預(yù)處理、用 CNN 或者 Transformer 類模型做分類、輸出結(jié)果。但真正做起來坑集中在數(shù)據(jù)集的類別不平衡、田間光照和背景干擾、模型在移動(dòng)端或者邊緣設(shè)備上的部署效率這幾個(gè)地方。下面我會(huì)按“數(shù)據(jù)怎么準(zhǔn)備、模型怎么選、代碼怎么寫、坑怎么避”的順序把一套可復(fù)現(xiàn)的方案講清楚。2. 數(shù)據(jù)集選型與預(yù)處理從 PlantVillage 到田間實(shí)拍圖的鴻溝2.1 為什么 PlantVillage 是起點(diǎn)但不是終點(diǎn)做農(nóng)業(yè)病蟲害識(shí)別繞不開 PlantVillage 這個(gè)數(shù)據(jù)集。它包含約 5 萬多張葉片圖像覆蓋 14 種作物、26 種病害背景大多是實(shí)驗(yàn)室條件下的純色背景葉片居中、光照均勻。對(duì)于剛?cè)腴T深度學(xué)習(xí)的人來說這個(gè)數(shù)據(jù)集非常友好因?yàn)樗念悇e標(biāo)注清晰圖像質(zhì)量穩(wěn)定訓(xùn)練一個(gè) ResNet 或者 MobileNet準(zhǔn)確率很容易沖到 95% 以上。但這里有一個(gè)血淚經(jīng)驗(yàn)在 PlantVillage 上跑到 99% 準(zhǔn)確率的模型拿到田間實(shí)拍圖上準(zhǔn)確率可能直接掉到 60% 以下。原因很簡(jiǎn)單田間圖像有復(fù)雜的背景、不均勻的光照、葉片遮擋、不同生長(zhǎng)階段的外觀變化。所以我的做法是用 PlantVillage 做預(yù)訓(xùn)練或者基線驗(yàn)證然后必須用自己采集的田間圖像做微調(diào)。如果拿不到田間數(shù)據(jù)至少要做數(shù)據(jù)增強(qiáng)模擬光照變化、旋轉(zhuǎn)、裁剪、噪聲。常見做法是先把 PlantVillage 按 8:1:1 劃分訓(xùn)練集、驗(yàn)證集、測(cè)試集保證每個(gè)類別在三個(gè)集合中的比例一致。如果某個(gè)類別的樣本數(shù)太少比如少于 200 張就要考慮過采樣或者用數(shù)據(jù)增強(qiáng)擴(kuò)充。2.2 用 Python 腳本完成數(shù)據(jù)劃分與增強(qiáng)下面這段代碼做三件事讀取 PlantVillage 的目錄結(jié)構(gòu)、按類別分層劃分?jǐn)?shù)據(jù)集、對(duì)訓(xùn)練集做實(shí)時(shí)增強(qiáng)。代碼依賴torchvision和splitfolders安裝命令是pip install torchvision splitfolders。import splitfolders from torchvision import transforms from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder # 第一步分層劃分?jǐn)?shù)據(jù)集保證每個(gè)類別在訓(xùn)練/驗(yàn)證/測(cè)試中的比例一致 # input_path 是原始數(shù)據(jù)目錄結(jié)構(gòu)為 root/類別名/圖片.jpg splitfolders.ratio( inputPlantVillage/raw, outputPlantVillage/split, seed42, ratio(0.8, 0.1, 0.1), # 訓(xùn)練:驗(yàn)證:測(cè)試 group_prefixNone ) # 第二步定義訓(xùn)練集和驗(yàn)證集的預(yù)處理流程 # 訓(xùn)練集用較強(qiáng)的增強(qiáng)驗(yàn)證集只做 resize 和歸一化 train_transform transforms.Compose([ transforms.Resize((224, 224)), # 統(tǒng)一輸入尺寸適配 ResNet/MobileNet transforms.RandomHorizontalFlip(p0.5), # 水平翻轉(zhuǎn)模擬不同拍攝角度 transforms.RandomRotation(15), # 小角度旋轉(zhuǎn)模擬葉片朝向變化 transforms.ColorJitter( # 模擬田間光照變化 brightness0.2, contrast0.2, saturation0.2, hue0.05 ), transforms.ToTensor(), transforms.Normalize( # ImageNet 均值方差遷移學(xué)習(xí)常用 mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) val_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) # 第三步構(gòu)建 Dataset 和 DataLoader train_dataset ImageFolder(PlantVillage/split/train, transformtrain_transform) val_dataset ImageFolder(PlantVillage/split/val, transformval_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4) print(f訓(xùn)練集類別數(shù): {len(train_dataset.classes)}) print(f訓(xùn)練集樣本數(shù): {len(train_dataset)}) print(f驗(yàn)證集樣本數(shù): {len(val_dataset)})這段代碼的關(guān)鍵參數(shù)有三個(gè)。ratio(0.8, 0.1, 0.1)是劃分比例如果數(shù)據(jù)集本身很小可以改成 7:1.5:1.5給驗(yàn)證集多一點(diǎn)樣本。Resize((224, 224))是輸入尺寸ResNet50 和 MobileNetV3 都支持這個(gè)尺寸如果要用 EfficientNet-B4可以改成 380。ColorJitter里的 brightness 和 contrast 不要設(shè)太大0.2 左右就夠了設(shè)太大反而會(huì)讓模型學(xué)到錯(cuò)誤的顏色關(guān)聯(lián)。注意劃分?jǐn)?shù)據(jù)集之前一定要檢查每個(gè)類別的樣本數(shù)。如果某個(gè)類別少于 100 張分層劃分后測(cè)試集可能只有不到 10 張?jiān)u估結(jié)果波動(dòng)會(huì)很大。這種情況建議先做數(shù)據(jù)增強(qiáng)擴(kuò)充或者用類別權(quán)重來緩解不平衡。2.3 田間圖像預(yù)處理的三個(gè)額外步驟如果你手頭有田間實(shí)拍圖預(yù)處理流程要加三步。第一步是背景分割用簡(jiǎn)單的顏色閾值或者 GrabCut 把葉片區(qū)域摳出來減少土壤、天空、地膜的干擾。第二步是光照歸一化用 CLAHE限制對(duì)比度自適應(yīng)直方圖均衡把過曝或者欠曝的圖像拉回來。第三步是尺寸篩選把分辨率低于 300x300 的圖像剔除因?yàn)榉糯蠛蠹?xì)節(jié)丟失嚴(yán)重模型學(xué)不到有效特征。這三步不需要每一步都做到完美但至少要做背景分割和光照歸一化。我試過直接拿原圖訓(xùn)練模型在驗(yàn)證集上的準(zhǔn)確率比做了背景分割的版本低了 8 個(gè)百分點(diǎn)原因就是模型把背景里的土壤顏色當(dāng)成了分類依據(jù)。3. 模型選型CNN、Transformer 還是輕量級(jí)網(wǎng)絡(luò)3.1 ResNet50、MobileNetV3、ViT 的取舍邏輯選模型這件事沒有絕對(duì)的最優(yōu)解只有適不適合你的場(chǎng)景。如果你的目標(biāo)是畢業(yè)設(shè)計(jì)需要跑出一個(gè)好看的準(zhǔn)確率同時(shí)論文里能畫出清晰的對(duì)比實(shí)驗(yàn)ResNet50 是最穩(wěn)妥的選擇。它的結(jié)構(gòu)經(jīng)典預(yù)訓(xùn)練權(quán)重容易獲取在 PlantVillage 上微調(diào)幾十個(gè) epoch 就能收斂。缺點(diǎn)是參數(shù)量大推理速度在 CPU 上偏慢。如果你要做移動(dòng)端或者邊緣設(shè)備部署比如把模型塞進(jìn)樹莓派或者安卓手機(jī)里MobileNetV3 或者 EfficientNet-Lite 更合適。MobileNetV3-Small 的參數(shù)量只有 2.5M 左右在驍龍 865 上單張推理時(shí)間可以壓到 20ms 以內(nèi)。代價(jià)是準(zhǔn)確率會(huì)比 ResNet50 低 2 到 4 個(gè)百分點(diǎn)具體低多少取決于你的數(shù)據(jù)集難度。ViTVision Transformer這兩年在圖像分類上很火但它對(duì)數(shù)據(jù)量的要求比 CNN 高得多。PlantVillage 只有 5 萬多張圖直接從頭訓(xùn)練 ViT 很容易過擬合。常見做法是用在 ImageNet 上預(yù)訓(xùn)練過的 ViT-B/16然后凍結(jié)前幾個(gè) block只微調(diào)后面的層。如果論文里想體現(xiàn)“前沿性”可以加一組 ViT 的對(duì)比實(shí)驗(yàn)但主力模型還是建議用 CNN。下面這張表是我在同一個(gè)數(shù)據(jù)集上跑出來的對(duì)比結(jié)果硬件是單卡 RTX 3060輸入尺寸 224x224訓(xùn)練 30 個(gè) epoch。模型參數(shù)量驗(yàn)證集準(zhǔn)確率單張推理時(shí)間CPU模型大小ResNet5025.6M96.8%85ms98MBMobileNetV3-Small2.5M93.2%18ms10MBEfficientNet-B05.3M95.1%32ms21MBViT-B/1686M94.5%210ms330MB從表里能看出來ResNet50 的準(zhǔn)確率最高但 MobileNetV3 的性價(jià)比最好。如果你的畢業(yè)設(shè)計(jì)需要部署演示用 MobileNetV3 做前端ResNet50 做后端對(duì)比論文里既有精度分析又有工程落地內(nèi)容會(huì)更完整。3.2 用 PyTorch 構(gòu)建遷移學(xué)習(xí)訓(xùn)練流程下面這段代碼用 ResNet50 做遷移學(xué)習(xí)凍結(jié)前面的卷積層只訓(xùn)練最后的全連接層和部分高層。代碼依賴torch、torchvision安裝命令是pip install torch torchvision。import torch import torch.nn as nn import torch.optim as optim from torchvision import models # 加載預(yù)訓(xùn)練 ResNet50num_classes 改成你的病害類別數(shù) num_classes 26 # PlantVillage 的 26 種病害 model models.resnet50(pretrainedTrue) # 凍結(jié)前 7 個(gè)層組只訓(xùn)練 layer4 和 fc # 這樣做的原因是底層特征邊緣、紋理通用性強(qiáng)不需要重新學(xué) for name, param in model.named_parameters(): if layer4 not in name and fc not in name: param.requires_grad False # 替換最后的全連接層輸出維度改成類別數(shù) model.fc nn.Linear(model.fc.in_features, num_classes) # 定義損失函數(shù)和優(yōu)化器 # CrossEntropyLoss 自帶 softmax不需要在模型里加 criterion nn.CrossEntropyLoss() # 只優(yōu)化需要梯度的參數(shù)學(xué)習(xí)率設(shè) 1e-3 optimizer optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lr1e-3, weight_decay1e-4 # L2 正則防止過擬合 ) # 學(xué)習(xí)率調(diào)度每 7 個(gè) epoch 衰減一次 scheduler optim.lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) # 訓(xùn)練循環(huán) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) for epoch in range(30): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f})這段代碼里有幾個(gè)參數(shù)需要根據(jù)你的實(shí)際情況調(diào)整。num_classes必須和你的數(shù)據(jù)集類別數(shù)一致如果做的是自定義數(shù)據(jù)集先統(tǒng)計(jì)一下類別數(shù)量。lr1e-3是 Adam 的常用初始學(xué)習(xí)率如果你解凍了更多層建議降到 1e-4否則容易破壞預(yù)訓(xùn)練權(quán)重。weight_decay1e-4是 L2 正則系數(shù)數(shù)據(jù)集越小這個(gè)值可以適當(dāng)調(diào)大比如 1e-3。提示凍結(jié)層數(shù)不是固定的。如果你的數(shù)據(jù)集和 ImageNet 差異很大比如葉片圖像和自然圖像的顏色分布完全不同可以只凍結(jié)前 3 個(gè)層組讓更多層參與訓(xùn)練。判斷方法是看訓(xùn)練集和驗(yàn)證集的準(zhǔn)確率差距如果訓(xùn)練集準(zhǔn)確率遠(yuǎn)高于驗(yàn)證集說明模型過擬合需要凍結(jié)更多層或者加正則。3.3 類別不平衡的處理加權(quán)損失與重采樣農(nóng)業(yè)病蟲害數(shù)據(jù)集有一個(gè)很現(xiàn)實(shí)的問題某些病害的樣本特別多某些特別少。比如 PlantVillage 里健康葉片的樣本數(shù)遠(yuǎn)多于某些罕見病害。如果直接訓(xùn)練模型會(huì)傾向于預(yù)測(cè)樣本多的類別導(dǎo)致稀有病害的召回率很低。常見做法有兩種。第一種是給損失函數(shù)加類別權(quán)重權(quán)重和樣本數(shù)成反比。第二種是用 WeightedRandomSampler 做重采樣讓每個(gè) batch 里稀有類別的樣本被抽到的概率更高。我一般會(huì)同時(shí)用這兩種方法先重采樣再加權(quán)損失。from torch.utils.data import WeightedRandomSampler import numpy as np # 統(tǒng)計(jì)每個(gè)類別的樣本數(shù) class_counts np.bincount(train_dataset.targets) class_weights 1.0 / class_counts sample_weights class_weights[train_dataset.targets] # 構(gòu)建 WeightedRandomSampler sampler WeightedRandomSampler( weightssample_weights, num_sampleslen(sample_weights), replacementTrue ) # 重新構(gòu)建 train_loader傳入 sampler train_loader DataLoader( train_dataset, batch_size32, samplersampler, # 注意用了 sampler 就不能再設(shè) shuffleTrue num_workers4 ) # 損失函數(shù)加類別權(quán)重 class_weights_tensor torch.FloatTensor(class_weights).to(device) criterion nn.CrossEntropyLoss(weightclass_weights_tensor)這段代碼的關(guān)鍵是class_weights 1.0 / class_counts樣本越少的類別權(quán)重越大。replacementTrue表示有放回抽樣保證每個(gè) epoch 都能抽到稀有類別的樣本。用了 sampler 之后DataLoader 的 shuffle 參數(shù)必須去掉否則會(huì)報(bào)錯(cuò)。4. 訓(xùn)練過程中的避坑與排查從 loss 不下降到過擬合4.1 現(xiàn)象訓(xùn)練 loss 震蕩不下降驗(yàn)證準(zhǔn)確率卡在 10% 左右原因通常有三個(gè)。第一學(xué)習(xí)率設(shè)太大了Adam 的默認(rèn)學(xué)習(xí)率是 1e-3但如果你的 batch size 很小比如 8 或者 161e-3 可能導(dǎo)致梯度爆炸。第二數(shù)據(jù)歸一化沒做對(duì)比如用了 ImageNet 的均值和方差但你的圖像像素值范圍是 0 到 255 而不是 0 到 1。第三標(biāo)簽有問題比如類別索引從 1 開始而不是從 0 開始導(dǎo)致 CrossEntropyLoss 計(jì)算錯(cuò)誤。解決方法是先把學(xué)習(xí)率降到 1e-4 試一下如果 loss 開始下降說明是學(xué)習(xí)率的問題。然后檢查transforms.ToTensor()是否在Normalize之前ToTensor 會(huì)把像素值從 0-255 縮放到 0-1。最后打印幾個(gè) batch 的標(biāo)簽確認(rèn)標(biāo)簽范圍是[0, num_classes-1]。4.2 現(xiàn)象訓(xùn)練準(zhǔn)確率 99%驗(yàn)證準(zhǔn)確率只有 60%這是典型的過擬合。原因可能是模型參數(shù)量太大、訓(xùn)練數(shù)據(jù)太少、增強(qiáng)不夠強(qiáng)。解決方法是先加數(shù)據(jù)增強(qiáng)比如 RandomResizedCrop、RandAugment然后加 Dropout在全連接層前面加nn.Dropout(0.5)最后用早停驗(yàn)證集 loss 連續(xù) 5 個(gè) epoch 不下降就停止訓(xùn)練。我一般會(huì)先看訓(xùn)練集和驗(yàn)證集的 loss 曲線。如果訓(xùn)練 loss 持續(xù)下降但驗(yàn)證 loss 開始上升說明過擬合已經(jīng)發(fā)生了這時(shí)候早停比繼續(xù)訓(xùn)練更劃算。4.3 現(xiàn)象模型在測(cè)試集上表現(xiàn)很好但實(shí)際拍照識(shí)別總是錯(cuò)原因通常是測(cè)試集和實(shí)際場(chǎng)景的分布不一致。PlantVillage 的測(cè)試集也是實(shí)驗(yàn)室條件和田間實(shí)拍圖差異很大。解決方法是在測(cè)試集里加入至少 20% 的田間圖像或者用域適應(yīng)的方法比如在訓(xùn)練時(shí)混合實(shí)驗(yàn)室和田間圖像讓模型學(xué)到更魯棒的特征。還有一個(gè)容易被忽略的點(diǎn)是圖像尺寸。訓(xùn)練時(shí)用的是 224x224 的居中裁剪但實(shí)際拍照時(shí)葉片可能不在畫面中央或者只占畫面的一小部分。這種情況需要在預(yù)處理階段加目標(biāo)檢測(cè)先把葉片框出來再送進(jìn)分類模型。4.4 現(xiàn)象GPU 顯存不夠batch size 只能設(shè)到 8原因可能是模型太大、輸入尺寸太大、或者沒有用混合精度訓(xùn)練。解決方法是先用torch.cuda.amp做混合精度顯存占用可以降低 30% 到 40%。然后把輸入尺寸從 224 降到 192 或者 160準(zhǔn)確率會(huì)掉 1 到 2 個(gè)百分點(diǎn)但顯存占用大幅下降。如果還是不夠就用梯度累積把 batch size 設(shè)成 8但每 4 個(gè) batch 才更新一次參數(shù)等效于 batch size 32。from torch.cuda.amp import autocast, GradScaler scaler GradScaler() for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() with autocast(): # 混合精度前向傳播 outputs model(images) loss criterion(outputs, labels) scaler.scale(loss).backward() # 縮放梯度 scaler.step(optimizer) scaler.update()混合精度訓(xùn)練的關(guān)鍵是autocast和GradScaler配合使用。autocast自動(dòng)把部分計(jì)算轉(zhuǎn)成 float16GradScaler防止梯度下溢。注意用了混合精度之后學(xué)習(xí)率可能需要稍微調(diào)大一點(diǎn)因?yàn)?float16 的精度損失會(huì)讓梯度變小。5. 從源碼到論文畢業(yè)設(shè)計(jì)里怎么把實(shí)驗(yàn)做扎實(shí)5.1 論文框架的四個(gè)必備章節(jié)如果你的畢業(yè)設(shè)計(jì)題目是“基于深度學(xué)習(xí)的常見農(nóng)作物病蟲害識(shí)別系統(tǒng)”論文框架可以按這個(gè)結(jié)構(gòu)搭。第一章緒論講研究背景和國(guó)內(nèi)外現(xiàn)狀引用幾篇近三年的農(nóng)業(yè)圖像分類論文。第二章相關(guān)技術(shù)介紹 CNN、遷移學(xué)習(xí)、注意力機(jī)制的基本原理不用寫太深但要把關(guān)鍵概念解釋清楚。第三章系統(tǒng)設(shè)計(jì)包括數(shù)據(jù)集構(gòu)建、預(yù)處理流程、模型結(jié)構(gòu)、訓(xùn)練策略。第四章實(shí)驗(yàn)與分析這是最核心的部分要有對(duì)比實(shí)驗(yàn)、消融實(shí)驗(yàn)、可視化結(jié)果。對(duì)比實(shí)驗(yàn)至少要有三組不同模型的對(duì)比ResNet vs MobileNet vs ViT、不同預(yù)處理方法的對(duì)比有無背景分割、有無數(shù)據(jù)增強(qiáng)、不同訓(xùn)練策略的對(duì)比凍結(jié)層數(shù)、學(xué)習(xí)率調(diào)度。消融實(shí)驗(yàn)可以看某個(gè)模塊的貢獻(xiàn)比如加了注意力機(jī)制之后準(zhǔn)確率提升了多少??梢暬Y(jié)果包括混淆矩陣、Grad-CAM 熱力圖、部分測(cè)試樣本的預(yù)測(cè)結(jié)果。5.2 用 Grad-CAM 做模型可解釋性分析Grad-CAM 是畢業(yè)設(shè)計(jì)里很加分的一個(gè)點(diǎn)它能告訴你模型在預(yù)測(cè)時(shí)關(guān)注了圖像的哪個(gè)區(qū)域。如果模型關(guān)注的是葉片上的病斑說明它學(xué)到了正確的特征如果關(guān)注的是背景或者葉片邊緣說明模型可能走了捷徑。from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image import numpy as np # 選擇目標(biāo)層ResNet50 通常選 layer4 的最后一個(gè) block target_layers [model.layer4[-1]] # 構(gòu)建 GradCAM 對(duì)象 cam GradCAM(modelmodel, target_layerstarget_layers) # 取一張測(cè)試圖像 input_tensor val_transform(image).unsqueeze(0).to(device) # 生成熱力圖 grayscale_cam cam(input_tensorinput_tensor, targetsNone) grayscale_cam grayscale_cam[0, :] # 疊加到原圖上 visualization show_cam_on_image( np.array(image) / 255.0, grayscale_cam, use_rgbTrue )這段代碼依賴pytorch-grad-cam庫(kù)安裝命令是pip install pytorch-grad-cam。target_layers選的是 ResNet50 的最后一個(gè)殘差塊這一層的特征圖分辨率是 7x7熱力圖會(huì)比較粗糙但語(yǔ)義信息強(qiáng)。如果想要更精細(xì)的熱力圖可以選 layer3但語(yǔ)義信息會(huì)弱一些。5.3 論文里怎么描述實(shí)驗(yàn)參數(shù)論文里的實(shí)驗(yàn)參數(shù)要寫清楚但不用把代碼全貼上去。我一般會(huì)用一個(gè)表格列出關(guān)鍵配置包括硬件環(huán)境、軟件版本、訓(xùn)練超參數(shù)、數(shù)據(jù)增強(qiáng)策略。比如配置項(xiàng)值GPUNVIDIA RTX 3060 12GB框架PyTorch 2.0輸入尺寸224x224Batch Size32初始學(xué)習(xí)率1e-3優(yōu)化器Adam學(xué)習(xí)率調(diào)度StepLR, step7, gamma0.1訓(xùn)練輪數(shù)30數(shù)據(jù)增強(qiáng)水平翻轉(zhuǎn)、隨機(jī)旋轉(zhuǎn)、顏色抖動(dòng)這樣寫的好處是審稿老師或者答辯評(píng)委能快速判斷你的實(shí)驗(yàn)是否合理也方便別人復(fù)現(xiàn)。如果用了預(yù)訓(xùn)練權(quán)重要注明是在 ImageNet 上預(yù)訓(xùn)練的因?yàn)檫@是遷移學(xué)習(xí)的基本前提。6. 部署與推理優(yōu)化讓模型在樹莓派上跑起來6.1 模型導(dǎo)出為 ONNX 并用 ONNX Runtime 推理訓(xùn)練完的 PyTorch 模型如果要部署到邊緣設(shè)備第一步通常是導(dǎo)出成 ONNX 格式。ONNX 是一個(gè)通用的模型交換格式支持多種推理引擎包括 ONNX Runtime、TensorRT、OpenVINO。import torch.onnx # 把模型設(shè)為評(píng)估模式 model.eval() # 構(gòu)造一個(gè)示例輸入 dummy_input torch.randn(1, 3, 224, 224).to(device) # 導(dǎo)出 ONNX torch.onnx.export( model, dummy_input, plant_disease_resnet50.onnx, export_paramsTrue, opset_version11, # ONNX 算子集版本11 兼容性較好 do_constant_foldingTrue, input_names[input], output_names[output], dynamic_axes{ # 動(dòng)態(tài) batch 維度方便批量推理 input: {0: batch_size}, output: {0: batch_size} } )導(dǎo)出之后用 ONNX Runtime 做推理速度比原生 PyTorch 在 CPU 上快 2 到 3 倍。opset_version11是一個(gè)比較穩(wěn)妥的選擇如果部署環(huán)境支持更高的版本可以改成 13 或者 15。dynamic_axes讓模型支持變長(zhǎng) batch推理時(shí)可以根據(jù)實(shí)際需求調(diào)整。6.2 樹莓派上的推理性能實(shí)測(cè)我在樹莓派 4B4GB 內(nèi)存上測(cè)過幾個(gè)模型的推理速度。ResNet50 的 ONNX 模型單張推理時(shí)間大約是 450msMobileNetV3-Small 大約是 80msEfficientNet-B0 大約是 150ms。如果要做實(shí)時(shí)檢測(cè)比如每秒處理 5 幀以上MobileNetV3 是唯一能在樹莓派上跑得比較流暢的選擇。優(yōu)化手段有三個(gè)。第一用 ONNX Runtime 的量化工具做 INT8 量化模型大小縮小到原來的四分之一推理速度提升 1.5 到 2 倍準(zhǔn)確率掉 1 到 2 個(gè)百分點(diǎn)。第二把輸入尺寸從 224 降到 160推理時(shí)間減少約 40%。第三用多線程推理ONNX Runtime 支持設(shè)置intra_op_num_threads樹莓派 4B 有 4 個(gè)核心設(shè)成 4 可以充分利用 CPU。import onnxruntime as ort # 配置 ONNX Runtime 會(huì)話 sess_options ort.SessionOptions() sess_options.intra_op_num_threads 4 # 使用 4 個(gè) CPU 核心 sess_options.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL session ort.InferenceSession( plant_disease_mobilenetv3.onnx, sess_optionssess_options, providers[CPUExecutionProvider] ) # 推理 input_name session.get_inputs()[0].name output session.run(None, {input_name: input_tensor.numpy()})graph_optimization_level設(shè)成ORT_ENABLE_ALL會(huì)啟用所有圖優(yōu)化包括算子融合、常量折疊、內(nèi)存復(fù)用。intra_op_num_threads設(shè)成 4 是因?yàn)闃漭?4B 有 4 個(gè)核心設(shè)太多反而會(huì)因?yàn)榫€程切換導(dǎo)致性能下降。6.3 一個(gè)容易被忽略的細(xì)節(jié)輸入圖像的預(yù)處理要對(duì)齊訓(xùn)練時(shí)的預(yù)處理和推理時(shí)的預(yù)處理必須完全一致否則準(zhǔn)確率會(huì)大幅下降。我踩過一次坑訓(xùn)練時(shí)用了ColorJitter做增強(qiáng)推理時(shí)忘了把圖像歸一化到相同的均值和方差結(jié)果模型把健康的葉片預(yù)測(cè)成了病害。后來在推理代碼里加了一個(gè)和驗(yàn)證集完全一樣的 transform問題才解決。推理時(shí)的預(yù)處理一般只做 Resize、ToTensor、Normalize不要加隨機(jī)翻轉(zhuǎn)或者顏色抖動(dòng)。如果你在訓(xùn)練時(shí)用了背景分割推理時(shí)也要做同樣的背景分割否則模型看到的輸入分布和訓(xùn)練時(shí)不一致。注意如果你的模型是在 PlantVillage 上訓(xùn)練的部署到田間時(shí)一定要先做背景分割。我見過太多模型在實(shí)驗(yàn)室數(shù)據(jù)上表現(xiàn)完美一到田間就翻車根本原因就是背景分布差異太大。6.4 最后說一個(gè)我自己的習(xí)慣每次訓(xùn)練完一個(gè)模型我不會(huì)只看最終的準(zhǔn)確率而是會(huì)把混淆矩陣打出來看看哪些類別之間容易混淆。比如霜霉病和靶斑病在混淆矩陣?yán)锝?jīng)?;ハ嗾`判。這時(shí)候我會(huì)針對(duì)性地補(bǔ)充這兩類病害的樣本或者設(shè)計(jì)一個(gè)二級(jí)分類器專門區(qū)分它們。這個(gè)習(xí)慣幫我省了很多返工的時(shí)間也讓論文里的分析更有深度。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取