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