:用CNN卷積神經(jīng)網(wǎng)絡(luò)實現(xiàn)昆蟲識別畢業(yè)設(shè)計全攻略)
什么時候一個畢業(yè)設(shè)計選題算是“好選題”我的標(biāo)準(zhǔn)很簡單技術(shù)路線成熟、工作量可控、數(shù)據(jù)不是難到找不著同時又有一個能講清楚的應(yīng)用故事。用Python做CNN卷積神經(jīng)網(wǎng)絡(luò)識別昆蟲恰好把這幾條占齊了。這個題目不是新得嚇人也不是舊得沒價值。昆蟲識別在農(nóng)業(yè)植保、生態(tài)監(jiān)測、檢疫防控里都有真實需求而深度學(xué)習(xí)圖像分類又是目前最成熟的方向之一數(shù)據(jù)和預(yù)訓(xùn)練模型都容易獲得。對于本科畢設(shè)或者碩士入門項目來說它相當(dāng)于一個“標(biāo)準(zhǔn)但絕不平庸”的練手場景——你把這個題目吃透圖像分類那一整套流程基本就通了。這篇文章我就從選題思路、數(shù)據(jù)準(zhǔn)備、模型搭建、訓(xùn)練優(yōu)化到最后的展示交付把我實際做這類項目踩過的坑、驗證過的做法全盤講一遍。1. 這個選題到底值不值得做先直接給結(jié)論值。而且對于絕大多數(shù)想認(rèn)真完成畢設(shè)、又不希望天天睡實驗室的同學(xué)來說它的性價比非常高。1.1 為什么是“昆蟲”而不是別的很多人一開始想的是貓狗識別、人臉識別、車型識別這些題目沒有不好問題是太“大眾”了答辯時老師一眼就能看到你做的是什么同質(zhì)化嚴(yán)重。換成“昆蟲”就不一樣它依然是一個標(biāo)準(zhǔn)的圖像分類任務(wù)但對象更具體、更有專業(yè)辨識度也更容易往“農(nóng)林業(yè)應(yīng)用”方向拔高。昆蟲識別的實際難點不在網(wǎng)絡(luò)結(jié)構(gòu)而在數(shù)據(jù)本身。昆蟲種類極多、外觀相似度高、姿態(tài)和環(huán)境背景復(fù)雜這反而成了這個題目的一大紅利同樣的模型在貓狗數(shù)據(jù)集上準(zhǔn)確率輕松上95%在昆蟲數(shù)據(jù)集上可能只有85%——這個差距不是壞事它能讓你在論文里“有東西可以分析”比如類間相似性導(dǎo)致的混淆、背景干擾的影響、細(xì)粒度分類的難度等等。說白了這個題目的“容錯空間”很大做得好了可以挖掘細(xì)粒度分類深度做得一般也能靠數(shù)據(jù)和預(yù)處理撐起工作量。1.2 三種主流技術(shù)方案的橫向?qū)Ρ任乙娺^不少同學(xué)拿到題目后第一步就想著“上大模型”其實大可不必。先看看主流方案的區(qū)別方案核心思路優(yōu)點缺點適合場景傳統(tǒng)圖像處理手動特征顏色直方圖、紋理特征、形狀描述子分類器無需GPU思路直觀泛化能力差準(zhǔn)確率低早期研究、對比實驗手工特征機器學(xué)習(xí)HOG/SIFTSVM特征提取交給算法分類用SVM比純規(guī)則好理論知識考查多對復(fù)雜背景敏感特征設(shè)計非常耗時證明傳統(tǒng)方法局限性的對比組深度學(xué)習(xí)CNN端到端自動學(xué)習(xí)特征準(zhǔn)確率高、流程標(biāo)準(zhǔn)化需要GPU、需要調(diào)參經(jīng)驗本選題主線絕大多數(shù)情況走這個我自己帶模擬項目的經(jīng)驗是不要死磕傳統(tǒng)方法但在論文里加一個“傳統(tǒng)方法對比”小節(jié)用SVM或簡單CNN跑一遍給個對照數(shù)據(jù)能讓工作量看起來更扎實。1.3 適合誰做、能鍛煉什么能力這個選題適合三類人一是本科階段準(zhǔn)備讀研、想提前熟悉深度學(xué)習(xí)完整流程的同學(xué)二是Python有一定基礎(chǔ)但沒系統(tǒng)做過CV項目的同學(xué)三是對農(nóng)業(yè)信息化方向感興趣、想在簡歷里留一個完整項目的同學(xué)。做這個題目你能摸到的關(guān)鍵技能包括數(shù)據(jù)采集與清洗、圖像預(yù)處理、Pytorch或TensorFlow的使用、模型訓(xùn)練與調(diào)參、模型評估與可視化、以及最后的部署交付。這套技能棧放到找實習(xí)或者考研復(fù)試?yán)锒际悄苤苯恿牡臇|西。2. 整體框架設(shè)計別急著寫代碼先想清楚這三件事我見過太多同學(xué)拿到數(shù)據(jù)集第一件事就是打開Jupyter Notebook開始訓(xùn)練訓(xùn)練完發(fā)現(xiàn)準(zhǔn)確率一塌糊涂然后開始懷疑人生。問題往往不在模型而在前期設(shè)計。2.1 類別設(shè)計分多少類、分哪幾類昆蟲識別的類別數(shù)量決定了整個項目的工作量和難度。我的建議是本科畢設(shè)控制在10到30類之間不要一上來就挑戰(zhàn)100類以上。原因很簡單類別太少5類以下顯得工作量不足答辯時不好講類別太多100類以上準(zhǔn)確率很難做好看而且訓(xùn)練時間和顯存需求都上去了10到30類是“跳一跳夠得著”的區(qū)間既能體現(xiàn)細(xì)粒度分類的難度又能把準(zhǔn)確率做到85%-95%的合理區(qū)間類別怎么選有三個思路按物種級別比如選20種常見農(nóng)業(yè)害蟲稻飛虱、棉鈴蟲、玉米螟等這個方向最有應(yīng)用價值按科級分類比如選10個科的昆蟲鞘翅目、鱗翅目、膜翅目等難度更低適合新手混合級別比如常見的蝴蝶、甲蟲、蜜蜂、蚊子各若干種數(shù)據(jù)最好找適合快速驗證還有一個容易被忽略的點類別的拉丁文學(xué)名一定要標(biāo)清楚。答辯老師很可能隨口問一句“你這個類是怎么劃分的”如果你能說出科屬種的關(guān)系印象分會好很多。2.2 技術(shù)棧選型Pytorch還是TensorFlow、用不用預(yù)訓(xùn)練模型圖像分類這個方向Pytorch和TensorFlow都能做但我的建議很明確如果你沒有特別的原因選Pytorch。理由有三個Pytorch的調(diào)試體驗更接近Python原生風(fēng)格斷點調(diào)試方便計算機視覺方向的最新論文和預(yù)訓(xùn)練模型大多優(yōu)先發(fā)布Pytorch版本從社區(qū)資源和查資料的角度Pytorch現(xiàn)在的生態(tài)已經(jīng)明顯占優(yōu)至于預(yù)訓(xùn)練模型的問題一句話就能說清不要自己從頭訓(xùn)練一個深層網(wǎng)絡(luò)。以ResNet50為例在ImageNet上預(yù)訓(xùn)練權(quán)重是官方下載好的你用遷移學(xué)習(xí)的方式微調(diào)權(quán)重在昆蟲數(shù)據(jù)集上訓(xùn)練幾輪就能收斂。如果從零訓(xùn)練同樣的數(shù)據(jù)量可能需要10倍以上的訓(xùn)練時間而且效果未必好。這是這個項目里性價比最高的決策。2.3 評估指標(biāo)的設(shè)定很多同學(xué)從頭到尾只盯一個準(zhǔn)確率Accuracy這是本題最常犯的錯誤。昆蟲識別這種數(shù)據(jù)天然存在類別不均衡問題比如“棉鈴蟲”的圖片明顯比“某種珍稀蜻蜓”多這時只看準(zhǔn)確率很容易被“騙”。我會建議至少同時看這四個指標(biāo)指標(biāo)作用特別說明Accuracy總體準(zhǔn)確率只能反映整體情況不均衡時失真Precision精確率對每個類別權(quán)重評估判定為某類的樣本中有多少是對的Recall召回率對每個類別的覆蓋評估某類的真實樣本有多少被找出來了F1-Score精確率與召回率的調(diào)和平均不均衡場景下最有參考價值另外混淆矩陣Confusion Matrix是答辯時的“神器”。把混淆矩陣可視化出來你能一眼看出哪兩類昆蟲經(jīng)常被搞混從而為你的論文增加一段非常自然的“錯誤分析與改進(jìn)方向”。3. 數(shù)據(jù)處理實操這步做不好模型再狂也沒用數(shù)據(jù)是整個項目里最花時間的環(huán)節(jié)沒有之一。我做過一次統(tǒng)計一個2萬張昆蟲圖片的項目數(shù)據(jù)清洗和整理花了將近一半的時間。很多同學(xué)容易低估這部分工作量結(jié)果后期訓(xùn)練時被數(shù)據(jù)的各種臟問題反復(fù)折磨。3.1 數(shù)據(jù)集從哪里來昆蟲數(shù)據(jù)集的獲取路徑大體有三種第一種用現(xiàn)成的公開數(shù)據(jù)集目前比較常用的是 AI Challenger 的農(nóng)業(yè)害蟲數(shù)據(jù)集、iNaturalist 的昆蟲子集、以及各類昆蟲圖像數(shù)據(jù)集。這類數(shù)據(jù)集的好處是已經(jīng)標(biāo)注好了壞處是可能存在標(biāo)注噪聲、背景單一樣等問題。用的時候要自己看一下圖片質(zhì)量比如分辨率過低、標(biāo)注明顯錯誤的可以直接刪掉。第二種自己爬取人工清洗用爬蟲工具從圖庫網(wǎng)站獲取圖片然后人工篩選。這條路有個很大的坑搜索引擎的圖片質(zhì)量參差不齊混入無關(guān)圖片的概率不小。一份項目里我見過一個“蜜蜂”類別里混進(jìn)了幾張黃蜂甚至蒼蠅的照片這樣訓(xùn)練出來的模型會在實際預(yù)測時出現(xiàn)莫名其妙的錯誤。第三種實地拍攝加分項如果你有條件自拍一部分真實場景下的昆蟲照片是很好的加分項。不需要拍幾百上千張哪怕每類拍幾十張統(tǒng)計上就能顯著提升模型在真實場景下的泛化能力。更重要的是答辯時你可以理直氣壯地說“這個數(shù)據(jù)集包含了一部分自己采集的真實野外場景數(shù)據(jù)”這在真實感維度上比純下載高出一截。3.2 目錄結(jié)構(gòu)與數(shù)據(jù)劃分這里我直接給出一個經(jīng)過驗證的目錄結(jié)構(gòu)模板insect_classification/ ├── data/ │ ├── train/ │ │ ├── bee/ │ │ ├── butterfly/ │ │ └── ... │ ├── val/ │ │ ├── bee/ │ │ ├── butterfly/ │ │ └── ... │ └── test/ │ ├── bee/ │ ├── butterfly/ │ └── ... ├── models/ ├── logs/ └── utils/數(shù)據(jù)劃分比例我一般用train : val : test 7 : 2 : 1。很多人把test當(dāng)val用訓(xùn)練時反復(fù)去看test的準(zhǔn)確率來調(diào)參這是學(xué)術(shù)倫理問題答辯時也會被老師抓。正確做法是val集合用于調(diào)參和早停判斷test集合只用一次放在最后評估最終模型性能。3.3 數(shù)據(jù)增強最便宜的好用技巧沒有數(shù)據(jù)增強的CNN訓(xùn)練在這個任務(wù)上幾乎可以說是“自斷一臂”。昆蟲識別中模型很容易過擬合到背景、光照、姿態(tài)等無關(guān)因素數(shù)據(jù)增強能有效緩解。我自己的增強策略是“適度組合”不會一上來全部拉滿增強手段參數(shù)設(shè)置建議適用場景隨機水平翻轉(zhuǎn)p0.5基本必加隨機旋轉(zhuǎn)±15度昆蟲姿態(tài)多變可加隨機裁剪縮放0.8~1.0模擬遠(yuǎn)近距離變化色彩抖動brightness/contrast ±20%自然光照變化高斯噪聲std0.01戶外圖像常帶噪聲CutMix / MixUp混合系數(shù)α0.2類別多時推薦用Pytorch實現(xiàn)的時候通常配合torchvision.transforms和albumentations庫。我的習(xí)慣是用albumentations做訓(xùn)練集增強換成torchvision的簡單transform做驗證集和測試集只做resize和歸一化。注意驗證集和測試集絕不能用訓(xùn)練集的強增強策略否則會污染評價結(jié)果。3.4 類別不均衡的三種應(yīng)對策略昆蟲數(shù)據(jù)很難完全均衡。這里講三種常用處理方式配合著用效果最好重采樣Oversampling / Undersampling少的類別重復(fù)抽樣多的類別隨機丟棄一部分。實現(xiàn)簡單但要注意別過度重復(fù)導(dǎo)致過擬合。類別權(quán)重Class Weight在損失函數(shù)里按類別樣本數(shù)反比設(shè)置權(quán)重。比如樣本數(shù)多的類別權(quán)重設(shè)為0.5少的設(shè)為2.0。Pytorch的CrossEntropyLoss里可以直接傳weight參數(shù)代碼很簡單。Focal Loss如果樣本不均衡特別嚴(yán)重可以考慮用Focal Loss代替交叉熵。它能自動降低已分類樣本的權(quán)重讓模型更關(guān)注困難樣本。缺點是自己實現(xiàn)時要調(diào)兩個超參調(diào)試成本略高。從實際效果看對于10~30類的昆蟲任務(wù)重采樣類別權(quán)重的組合通常就能達(dá)到可接受的效果Focal Loss屬于“進(jìn)一步提升”的手段。4. 模型搭建與訓(xùn)練全流程現(xiàn)在到了真正的重頭戲。這一部分我會把從加載數(shù)據(jù)到訓(xùn)練完成的整個流程完整過一遍關(guān)鍵的代碼直接貼出來并解釋每一處為什么這么寫。4.1 基礎(chǔ)模型的選型ResNet50是穩(wěn)妥之選在昆蟲識別這種細(xì)粒度分類任務(wù)上模型的“大小”不是越大越好。我推薦的第一選擇是ResNet50理由是平衡性好參數(shù)量適中約26M訓(xùn)練速度可控預(yù)訓(xùn)練權(quán)重容易獲取而且ResNet的殘差結(jié)構(gòu)在特征傳遞上有天然優(yōu)勢對細(xì)粒度特征的捕捉能力經(jīng)過大量工業(yè)驗證。比ResNet50更輕的選擇是MobileNetV3或EfficientNet適合資源受限環(huán)境但準(zhǔn)確率會低一些比ResNet50更大的選擇是EfficientNet-B4-B7或Swin Transformer效果理論上更好但訓(xùn)練時間和顯存成本直線上升。對于畢設(shè)來說我不建議一上來就挑戰(zhàn)Transformer類模型——不是不讓你用而是這個技術(shù)選型會大幅增加調(diào)試成本和計算資源需求性價比不高。4.2 數(shù)據(jù)集加載與預(yù)處理代碼Pytorch加載數(shù)據(jù)用的是torch.utils.data.DataLoader和torchvision.datasets.ImageFolder。這里有一個值得強調(diào)的經(jīng)驗數(shù)據(jù)加載腳本盡量一開始就寫好完整的不要用一遍改一遍的“臨時腳本”。因為數(shù)據(jù)集一旦分割完成后續(xù)又要從頭加載浪費時間不說還容易出錯。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 訓(xùn)練集增強 train_transforms transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 驗證/測試集只做尺寸歸一化不做增強 val_transforms transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset datasets.ImageFolder(rootdata/train, transformtrain_transforms) val_dataset datasets.ImageFolder(rootdata/val, transformval_transforms) test_dataset datasets.ImageFolder(rootdata/test, transformval_transforms) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4) test_loader DataLoader(test_dataset, batch_size32, shuffleFalse, num_workers4)這里有個新手容易踩的坑Resize((224, 224))是直接拉伸如果原始圖片寬高比不是1:1昆蟲會被壓變形。更推薦的做法是Resize(256)之后再CenterCrop(224)或者RandomResizedCrop(224)這樣能保證主體比例更自然。不過由于這類數(shù)據(jù)集的圖片主要來源于網(wǎng)絡(luò)采集多數(shù)接近方形所以直接Resize問題也不大但提前知道這個區(qū)別對你理解數(shù)據(jù)預(yù)處理有幫助。4.3 模型搭建遷移學(xué)習(xí)的正確姿勢遷移學(xué)習(xí)的核心思路是保留預(yù)訓(xùn)練模型在前置層學(xué)到的通用特征邊緣、紋理、形狀然后更換最后的全連接分類頭讓它適應(yīng)自己的分類數(shù)。import torch.nn as nn from torchvision import models # 加載預(yù)訓(xùn)練ResNet50 model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) # 替換最后一層全連接改為自己的類別數(shù) num_classes len(train_dataset.classes) model.fc nn.Linear(model.fc.in_features, num_classes) # 將模型放到GPU上 device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)很多人問要不要把整個網(wǎng)絡(luò)全部解凍訓(xùn)練還是只微調(diào)最后一兩層。我的經(jīng)驗是分兩步走第一步凍結(jié)特征層把除了最后一層之外的參數(shù)都設(shè)為requires_gradFalse只訓(xùn)練自己加的分類頭。用較小的學(xué)習(xí)率比如1e-3訓(xùn)練幾個epoch這相當(dāng)于讓新加的線性層先熟悉特征的分布。第二步解凍全部層把整個網(wǎng)絡(luò)的參數(shù)都設(shè)為requires_gradTrue用更小的學(xué)習(xí)率1e-4到5e-5繼續(xù)訓(xùn)練全部層。這是因為解凍后如果學(xué)習(xí)率還是很大很可能直接破壞預(yù)訓(xùn)練權(quán)重。實際這兩步的訓(xùn)練時間不會太長但效果比一上來就全量微調(diào)要穩(wěn)定得多。4.4 損失函數(shù)、優(yōu)化器、學(xué)習(xí)率調(diào)度import torch.optim as optim # 損失函數(shù)類別不均衡就加權(quán)重 class_weights torch.tensor([...], devicedevice) # 按樣本數(shù)反比計算 criterion nn.CrossEntropyLoss(weightclass_weights) # 優(yōu)化器AdamW比Adam更穩(wěn)防止過擬合效果更好 optimizer optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) # 學(xué)習(xí)率調(diào)度余弦退火衰減 scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30, eta_min1e-6)關(guān)于優(yōu)化器的選擇我確實試過SGD和AdamW兩種方案。SGD動量在圖像分類上從理論上看收斂更穩(wěn)但需要精心調(diào)學(xué)習(xí)率和動量參數(shù)對于沒有大量調(diào)參經(jīng)驗的同學(xué)AdamW幾乎是“開了自動擋”省心得多。余弦退火調(diào)度器比固定學(xué)習(xí)率的效果好很多它能讓模型在前半段大步搜索、后半段精細(xì)收斂在昆蟲識別這種“類間差異比較小”的任務(wù)上作用尤為明顯。如果不理解這些細(xì)節(jié)直接照抄這一段問題不大。4.5 訓(xùn)練循環(huán)與模型保存def train_one_epoch(model, train_loader, criterion, optimizer, device): model.train() total_loss, correct, total 0, 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() total_loss loss.item() * images.size(0) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) return total_loss / total, correct / total def evaluate(model, val_loader, criterion, device): model.eval() total_loss, correct, total 0, 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) total_loss loss.item() * images.size(0) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) return total_loss / total, correct / total # 主訓(xùn)練循環(huán) epochs 30 # 這是基線輪數(shù)實際要看損失曲線調(diào)整 best_acc 0.0 for epoch in range(epochs): train_loss, train_acc train_one_epoch(model, train_loader, criterion, optimizer, device) val_loss, val_acc evaluate(model, val_loader, criterion, device) scheduler.step() print(fEpoch {epoch1}/{epochs} | Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth) print(f - 已保存最佳模型val_acc{val_acc:.4f})訓(xùn)練時有一個非常關(guān)鍵的習(xí)慣每個epoch結(jié)束都要在驗證集上評估一次并且只在驗證集準(zhǔn)確率創(chuàng)新高時保存模型。這叫“模型選擇”能夠避免訓(xùn)練到最后過擬合導(dǎo)致val準(zhǔn)確率下降卻把最后一輪的糟糕模型留在手里。4.6 訓(xùn)練過程的檢查清單我每訓(xùn)練一個新模型都會盯著幾個指標(biāo)看一旦發(fā)現(xiàn)異常立刻停訓(xùn)練loss是否平穩(wěn)下降如果前幾個epoch loss反而上漲十有八九是學(xué)習(xí)率太大或代碼有bug訓(xùn)練acc與驗證acc的差距如果差距超過10%以上說明嚴(yán)重過擬合驗證acc是否震蕩很大可能批次太小、數(shù)據(jù)噪聲較大或類別不均衡嚴(yán)重訓(xùn)練時間是否在合理范圍單epoch遠(yuǎn)超預(yù)期時先檢查DataLoader的num_workers和CPU/GPC占用情況有次做模擬項目時我發(fā)現(xiàn)訓(xùn)練acc很快逼近100%但驗證acc只有70%左右這就是典型的過擬合信號。當(dāng)時做的最有效的處理是增強數(shù)據(jù)增強的強度并加大weight_decay到1e-3。這個經(jīng)驗可以套用到絕大多數(shù)昆蟲識別項目上。5. 提升準(zhǔn)確率的進(jìn)階優(yōu)化方法如果你只想要一個“能交差”的模型做到上面那部分其實已經(jīng)夠了。但如果你希望在答辯時能挺直腰板講“我這個課題不只是跑通了一個網(wǎng)絡(luò)”那這一部分的內(nèi)容就要好好琢磨了。5.1 細(xì)粒度分類思路昆蟲任務(wù)的關(guān)鍵難點昆蟲識別比一般的貓狗分類難在“類間相似”。比如菜粉蝶和云粉蝶、螳螂和竹節(jié)蟲外形差異真的很細(xì)微普通CNN只做全局特征分類很容易弄混。解決這類細(xì)粒度分類Fine-Grained Image Classification有幾個常見思路多尺度特征融合讓模型同時看到昆蟲整體的輪廓和身體局部的紋理。實現(xiàn)上可以在ResNet的某些中間層額外引出一個分支把淺層高分辨率特征和深層語義特征拼接在一起。常見的做法是改用類似ResNetFPN的結(jié)構(gòu)做特征融合。注意力機制在主干網(wǎng)絡(luò)后面加一個Squeeze-and-Excitation模塊或CBAM注意力模塊讓模型更關(guān)注昆蟲的判別性區(qū)域比如翅脈紋理、觸角形態(tài)。這個改動很小代碼量也不多但經(jīng)常能有1-2個百分點的提升。雙線性池化這是數(shù)年前細(xì)粒度分類的標(biāo)準(zhǔn)做法之一用兩個特征提取器的外積來建模局部特征的二階統(tǒng)計信息效果不錯但計算量偏大、訓(xùn)練維度很高現(xiàn)在用的人不多了。對于本科畢設(shè)來說我建議最多在架構(gòu)里加一個注意力模塊比如直接在ResNet的每個Block后加SE就足夠了既能體現(xiàn)你對細(xì)粒度分類的理解又不會給自己挖太大的坑。5.2 用Grad-CAM做可視化分析Grad-CAM是答辯環(huán)節(jié)的“大殺器”。它能把模型做分類時的注意力區(qū)域以熱力圖形式畫出來直觀顯示模型是依據(jù)昆蟲的哪個部位做出判斷。這在論文里放兩張圖整個工作的可信度和深度都不一樣了。import numpy as np import cv2 import torch from torchvision import models, transforms # 選取目標(biāo)層ResNet50最后一個卷積層是 layer4[2].conv3 model._modules.get(layer4) target_layer model.layer4[-1] def grad_cam(model, input_tensor, target_layer): gradients [] activations [] def forward_hook(module, input, output): activations.append(output) def backward_hook(module, grad_input, grad_output): gradients.append(grad_output[0]) hook_f target_layer.register_forward_hook(forward_hook) hook_b target_layer.register_backward_hook(backward_hook) output model(input_tensor) pred_class output.argmax(dim1).item() model.zero_grad() one_hot torch.zeros_like(output) one_hot[0][pred_class] 1 output.backward(gradientone_hot) hook_f.remove() hook_b.remove() weights gradients[0].mean(dim(2, 3), keepdimTrue) cam (weights * activations[0]).sum(dim1, keepdimTrue) cam torch.relu(cam).squeeze() cam cam.detach().cpu().numpy() cam (cam - cam.min()) / (cam.max() - cam.min()) return cam # 使用時用opencv把熱力圖疊加到原圖上用Grad-CAM做錯誤樣本分析非常有意思比如你發(fā)現(xiàn)模型把某種甲蟲錯認(rèn)成另一種甲蟲看熱力圖就會發(fā)現(xiàn)模型在看它們同樣色澤的鞘翅而忽略了腿部的明顯差異。這個分析寫進(jìn)論文的“錯誤分析”章節(jié)比空泛地說“可能是特征不夠”強太多了。5.3 模型集成的簡單做法要不要做集成Ensemble我的觀點是如果答辯時間緊做簡單一點的集成可以穩(wěn)定提高1-3個百分點但如果時間很緊做一個模型也完全夠。集成最簡單的形式是“投票法”用兩個模型比如ResNet50和EfficientNet-B0分別預(yù)測最后按類別概率加和取最大概率的類別。實際項目中我用的集成策略是這樣的單獨訓(xùn)練ResNet50做baseline單獨訓(xùn)練EfficientNet-B0做對照模型預(yù)測階段把兩個模型的輸出概率做加權(quán)平均權(quán)重分別是0.6和0.4綜合得到最終預(yù)測這個做法的好處除了準(zhǔn)確率略高還能寫出“不同結(jié)構(gòu)模型在不同類別上的互補性”很有論文味。缺點就是訓(xùn)練時間翻倍好在現(xiàn)在的GPU資源比較充裕跑一個輕量模型不算太久。6. 展示與部署交付一份超出預(yù)期的畢設(shè)很多同學(xué)把模型訓(xùn)練完、準(zhǔn)確率夠了就直接寫論文覺得萬事大吉。但我的經(jīng)驗是畢設(shè)是一個“項目”不是一篇“報告”。如果能在交付物里加一個可交互的Web界面或者Demo程序給老師的印象會完全不一樣。6.1 用Flask做一個簡單的Web識別界面Python做Web最簡單的答案就是Flask。模型推理的過程可以封裝在一個函數(shù)里然后通過Web接口暴露出來。核心代碼不復(fù)雜from flask import Flask, request, jsonify, render_template from PIL import Image import torch import torchvision.transforms as transforms app Flask(__name__) # 模型加載放在全局變量只做一次 model load_trained_model(best_model.pth) class_names load_class_names(class_names.txt) app.route(/, methods[GET, POST]) def index(): if request.method POST: file request.files[image] image Image.open(file.stream).convert(RGB) # 預(yù)處理并預(yù)測 ... return jsonify({prediction: class, confidence: prob}) return render_template(index.html)這個界面不需要很花哨居中放一個上傳框點擊圖片后展示識別結(jié)果和置信度就夠用了。如果你對前端不熟完全可以用H5寫一個極簡頁面不要被“Web開發(fā)”這個詞嚇到。6.2 打包成桌面/Demo程序的可選方案如果你不太擅長Web另一個思路是用Streamlit這個框架對Python開發(fā)者特別友好代碼量比Flask還少。比如import streamlit as st from PIL import Image st.title(昆蟲識別系統(tǒng)) uploaded st.file_uploader(上傳昆蟲圖片, type[jpg, png]) if uploaded is not None: image Image.open(uploaded) st.image(image, caption上傳的圖片, use_column_widthTrue) pred, conf predict(image) st.write(f識別結(jié)果{pred}置信度{conf:.2%})Streamlit的魅力在于你不需要寫HTML和JavaScript只要把“讀取圖片→預(yù)處理→模型推理→展示結(jié)果”用Python寫完它自動幫你渲染成網(wǎng)頁。用在畢設(shè)Demo上非?!澳艽颉?。6.3 可視化與論文配圖論文里除了必要的圖表之外還建議制作一張“類別識別效果”的典型圖片矩陣。做法很簡單每個類別挑兩三張代表性圖片上面標(biāo)注模型預(yù)測的類別和置信度。這個圖放論文正文或者答辯PPT里直觀易懂還顯得你特別用心。混淆矩陣的熱力圖也是必備推薦用matplotlib配合sklearn.metrics.confusion_matrix生成配色選暖色調(diào)一眼就能看出分類問題在哪里。7. 常見問題與排查技巧實錄最后這部分可能是對你最有直接幫助的。我把做這個項目時比較容易踩的坑按問題現(xiàn)象、原因分析和解決辦法整理成一份“速查表”建議你收藏備用。7.1 訓(xùn)練過程不收斂或者loss飛了現(xiàn)象描述loss不降反升或者訓(xùn)練acc在50%左右來回震蕩遲遲上不去??赡茉蛉绾闻袛嘟鉀Q方式學(xué)習(xí)率過大前幾個epoch loss劇烈波動把初始學(xué)習(xí)率降低1個量級從1e-4降到1e-5數(shù)據(jù)處理出錯某些標(biāo)簽和圖片對應(yīng)錯亂隨機抽幾張訓(xùn)練樣本可視化檢查標(biāo)簽匹配模型沒有正確加載預(yù)訓(xùn)練權(quán)重早期acc就低于10%打印模型權(quán)重確認(rèn)fc層是自己的分類數(shù)歸一化參數(shù)錯誤圖片在transform里變成了黑圖或噪聲圖檢查mean/std用的ImageNet標(biāo)準(zhǔn)值是否正確其中最容易忽視的是“數(shù)據(jù)打亂不徹底”。如果你用ImageFolder讀取數(shù)據(jù)時忘了shuffleTrue那每個batch里的樣本其實都是按文件夾順序來的模型會學(xué)到一種非常虛假的“順序規(guī)律”驗證集上則一塌糊涂。7.2 類別不均衡導(dǎo)致小類完全識別不出這是昆蟲任務(wù)里非常典型的問題。解法思路在前面已經(jīng)提過這里給一個最優(yōu)的操作順序先統(tǒng)計各類別樣本數(shù)確認(rèn)不均衡程度給CrossEntropyLoss加weight如果效果還是不行用重采樣把少類的圖片重復(fù)讀入最后如果還不行考慮Focal Loss我自己的經(jīng)驗是第2步通常能解決大部分問題第3步容易引入過擬合因為重復(fù)樣本會讓小類記憶化所以配合數(shù)據(jù)增強使用第4步是最后的兜底手段。7.3 顯存不夠怎么辦如果你的GPU只有顯存較小的卡或者根本只有CPU也不是完全沒轍減小batch_size比如從32降到8或4這是最直接的辦法換輕量模型比如EfficientNet-B0、MobileNetV3開啟混合精度訓(xùn)練Pytorch里的torch.cuda.amp能省將近一半顯存且速度更快凍結(jié)前面大部分層只訓(xùn)練最后分類層能大幅減少顯存占用對CPU訓(xùn)練我要潑一盆冷水ResNet50在純CPU上跑30輪每類20張、總共3000張數(shù)據(jù)可能要跑十幾個小時。如果確實沒有GPU建議用Google官方出品的Colab免費GPU云服務(wù)或者AWS的免費額度這都是很多開發(fā)者常用的方式能很大程度緩解算力焦慮。7.4 訓(xùn)練很久但測試集準(zhǔn)確率不高如果你在驗證集上評估得還不錯比如88%但測試集準(zhǔn)確率只有70%原因幾乎總是“測試集分布和訓(xùn)練集差異太大”。解決方法有兩個檢查test數(shù)據(jù)是否預(yù)處理正確尤其是刪除過強增強操作如果test圖片來自模型沒見過的場景比如燈光很暗、背景雜亂最簡單有效的方式是再做一次模型外部的數(shù)據(jù)清洗將質(zhì)量明顯偏差的測試圖片剔除或補充同類數(shù)據(jù)把分布拉回來另外不要忘了早停的重要性。我在一個項目中遇到的最佳epoch是第17輪而如果訓(xùn)練滿30輪再保存后面幾輪已經(jīng)在過擬合了。所以“每輪都評估、只存最優(yōu)”這個習(xí)慣價值非常高。7.5 答辯時最容易被問的幾個問題結(jié)合我評審和旁聽過多次答辯的經(jīng)驗老師圍繞這個選題通常愛問這些建議提前準(zhǔn)備好答案為什么選擇CNN而不是傳統(tǒng)機器學(xué)習(xí)算法識別昆蟲你的模型性能瓶頸在哪幾類昆蟲之間為什么和其他分類方案相比你的方法準(zhǔn)確率提升的原因是什么如果數(shù)據(jù)量擴大10倍你的方案要做什么調(diào)整如果要把你的模型部署到田間地頭的移動設(shè)備上有什么限制這些問題都回答得好的前提是你確實清楚自己做了哪些實驗、每個實驗結(jié)果是什么、優(yōu)缺點在哪里。如果有Grad-CAM可視化圖、混淆矩陣、多指標(biāo)評估表格放在PPT里每個問題都能對號入座地答上。最后做這個題目時我最想提醒你的一點做這種畢業(yè)設(shè)計題目最容易犯的錯不是不會寫代碼而是拿到一個現(xiàn)成的模型就開跑從頭到尾只是把數(shù)據(jù)集喂進(jìn)去、看準(zhǔn)確率。如果只是為了跑通那個準(zhǔn)確率數(shù)字這個題目就變成了純粹的體力活。我在實際做這類項目時最大的體會是把每一個“為什么”都搞清楚才能真正把項目變成自己的東西。比如學(xué)習(xí)率為什么不能設(shè)得太大、為什么小類別的召回率總是不高、為什么模型會把某種蜜蜂和某種蠅類搞混。這些問題不是論文里的空話而是你真正調(diào)試時會一次次碰到的東西。當(dāng)你把這些原因和解決辦法寫進(jìn)論文那么這篇畢設(shè)論文就不再是“用CNN做了一個昆蟲分類”這種一句話能講完的題目而是一個有分析、有對比、有說服力的完整工作。如果你決定做這個題目我的最后建議是先別急著下載數(shù)據(jù)先把類別設(shè)計、評估指標(biāo)、設(shè)備環(huán)境這三件事敲定下來。這三件事定了后面所有代碼和實驗都只是執(zhí)行問題。祝順利。