習(xí)的水果成熟度識(shí)別:從數(shù)據(jù)集到部署的畢設(shè)全攻略)
這幾年每到畢業(yè)季都有不少同學(xué)來(lái)問(wèn)我同一個(gè)問(wèn)題深度學(xué)習(xí)方向的畢業(yè)設(shè)計(jì)選什么課題既好出成果、又不至于爛大街如果你也正在糾結(jié)恰好又對(duì)圖像識(shí)別、Python生態(tài)比較熟那“基于深度學(xué)習(xí)的水果成熟度識(shí)別”是一個(gè)性?xún)r(jià)比很高的選擇。這類(lèi)題目拿公開(kāi)數(shù)據(jù)集就能跑通全流程從數(shù)據(jù)預(yù)處理、模型訓(xùn)練、性能評(píng)估到最后的Web端或小程序演示能完整覆蓋本科或碩士畢設(shè)的大部分技術(shù)要求而且成果展示起來(lái)非常直觀——識(shí)別一串葡萄或者一個(gè)芒果熟沒(méi)熟比在PPT里貼幾張loss曲線(xiàn)有說(shuō)服力得多。我這兩年陸續(xù)帶過(guò)幾個(gè)做類(lèi)似方向的同學(xué)自己也完整復(fù)現(xiàn)過(guò)一套蘋(píng)果成熟度分級(jí)的小項(xiàng)目。這篇文章就把整個(gè)實(shí)操鏈路拆開(kāi)來(lái)講從課題價(jià)值分析、數(shù)據(jù)集怎么挑怎么增強(qiáng)到模型選型、訓(xùn)練調(diào)參、界面封裝再到論文怎么寫(xiě)、答辯怎么講一條龍說(shuō)清楚。文章里給出的數(shù)據(jù)和配置都是我自己跑過(guò)的方案你照著改一改就能用。1. 課題拆解為什么“水果成熟度識(shí)別”適合做畢設(shè)1.1 這個(gè)課題到底在解決什么問(wèn)題大多數(shù)人看到“水果成熟度識(shí)別”第一反應(yīng)是“不就是對(duì)著一張圖分類(lèi)嗎”但實(shí)際動(dòng)手后會(huì)發(fā)現(xiàn)它比普通的貓狗分類(lèi)要微妙很多。常規(guī)分類(lèi)任務(wù)里的類(lèi)別差異是顯著的比如貓和狗在形態(tài)、紋理上天然不同而水果成熟度的差異往往是漸變的同一品種的果實(shí)在不同成熟階段的顏色、光澤、紋理變化幅度可能不大再加上拍攝光照、角度、遮擋的干擾模型很容易在“接近成熟”和“完全成熟”這兩類(lèi)之間翻車(chē)。這正是這個(gè)課題真正的工程價(jià)值所在它考察的不是“能不能訓(xùn)練出一個(gè)分類(lèi)器”而是“在類(lèi)別邊界模糊的條件下你怎么設(shè)計(jì)數(shù)據(jù)、怎么定義標(biāo)簽、怎么評(píng)估模型”。放到畢設(shè)場(chǎng)景下這就意味著你有充分的實(shí)驗(yàn)空間可以展開(kāi)——可以比較不同模型架構(gòu)、不同數(shù)據(jù)增強(qiáng)策略、不同損失函數(shù)對(duì)邊界樣本的影響這些都能成為論文里有理有據(jù)的章節(jié)而不是簡(jiǎn)單堆一堆訓(xùn)練日志。1.2 從就業(yè)和應(yīng)用角度看這個(gè)題目的含金量從就業(yè)角度說(shuō)水果成熟度識(shí)別屬于“農(nóng)業(yè)視覺(jué)”這個(gè)大方向和工業(yè)質(zhì)檢、醫(yī)療影像診斷本質(zhì)上是同一類(lèi)問(wèn)題——都是判斷“對(duì)象狀態(tài)是否達(dá)標(biāo)”。你在這個(gè)項(xiàng)目里練出來(lái)的數(shù)據(jù)清洗能力、遷移學(xué)習(xí)調(diào)參經(jīng)驗(yàn)、模型部署思路換一個(gè)場(chǎng)景照樣適用。很多做智慧農(nóng)業(yè)、智能分揀設(shè)備的公司核心算法需求就是這類(lèi)狀態(tài)識(shí)別任務(wù)。從落地角度看小型的果園、生鮮電商倉(cāng)庫(kù)、農(nóng)產(chǎn)品收購(gòu)站都有人工分揀的需求。人工分揀的問(wèn)題在于標(biāo)準(zhǔn)不統(tǒng)一——同一個(gè)筐里張三覺(jué)得這個(gè)蘋(píng)果七成熟李四覺(jué)得已經(jīng)八成熟了收購(gòu)價(jià)就差出一截。一個(gè)可以量化、可以復(fù)現(xiàn)的判斷標(biāo)準(zhǔn)確實(shí)是實(shí)際業(yè)務(wù)里愿意付費(fèi)的功能。做畢設(shè)的時(shí)候把這個(gè)應(yīng)用場(chǎng)景寫(xiě)清楚本身就是給論文加分的“社會(huì)價(jià)值”段落。1.3 適合什么樣基礎(chǔ)的同學(xué)做這個(gè)題目對(duì)本科畢設(shè)來(lái)說(shuō)難度適中對(duì)碩士畢設(shè)來(lái)說(shuō)可以做深。如果你已經(jīng)能用PyTorch跑通一個(gè)簡(jiǎn)單的分類(lèi)模型理解dataset、dataloader、model、train loop這套基本流程那這個(gè)題目的代碼門(mén)檻就沒(méi)什么問(wèn)題了。如果你還處于“學(xué)過(guò)原理但沒(méi)完整寫(xiě)過(guò)訓(xùn)練腳本”的階段也不用慌這個(gè)項(xiàng)目的代碼量不大核心訓(xùn)練腳本大概200行以?xún)?nèi)更多的時(shí)間會(huì)花在數(shù)據(jù)整理和實(shí)驗(yàn)對(duì)比上適合一邊做一邊補(bǔ)基礎(chǔ)。2. 數(shù)據(jù)準(zhǔn)備成熟度識(shí)別項(xiàng)目的命門(mén)所在2.1 公開(kāi)數(shù)據(jù)集怎么挑、怎么繞過(guò)坑做水果成熟度識(shí)別第一步不是選模型而是選數(shù)據(jù)。公開(kāi)數(shù)據(jù)集方面比較常見(jiàn)的包括某高校公開(kāi)的蘋(píng)果成熟度數(shù)據(jù)集、某開(kāi)放平臺(tái)上的柑橘成熟度數(shù)據(jù)集、以及一些綜合果蔬分類(lèi)數(shù)據(jù)集中帶成熟度標(biāo)注的子集。用公開(kāi)數(shù)據(jù)集的優(yōu)點(diǎn)是省去了標(biāo)注的麻煩缺點(diǎn)也很明顯圖片數(shù)量少、拍攝場(chǎng)景單一、類(lèi)別分布往往不均衡。選數(shù)據(jù)集的時(shí)候有幾個(gè)硬性標(biāo)準(zhǔn)。第一每個(gè)類(lèi)別至少要有300張以上圖片這樣配合數(shù)據(jù)增強(qiáng)才能撐起一個(gè)像樣的訓(xùn)練第二圖片分辨率不能太低至少保證果實(shí)主體區(qū)域的像素在50x50以上否則模型很難學(xué)到紋理層面的特征第三盡量選有“接近成熟”這類(lèi)中間狀態(tài)標(biāo)注的因?yàn)槌墒炫c未成熟的兩極區(qū)分太簡(jiǎn)單體現(xiàn)不出你調(diào)參的功夫而中間狀態(tài)的識(shí)別準(zhǔn)確率恰恰是你論文里最出彩的實(shí)驗(yàn)對(duì)比點(diǎn)。2.2 數(shù)據(jù)清洗和標(biāo)注級(jí)聯(lián)的細(xì)節(jié)公開(kāi)數(shù)據(jù)集的標(biāo)注質(zhì)量參差不齊動(dòng)手前一定要自己過(guò)一遍。我常用的流程是先把所有圖片統(tǒng)一縮放到256x256分辨率然后逐張看扔掉三類(lèi)圖片——嚴(yán)重模糊的、果實(shí)占比過(guò)小的小于畫(huà)面10%、標(biāo)注明顯錯(cuò)誤的。這個(gè)環(huán)節(jié)看似枯燥卻是整個(gè)項(xiàng)目里投入產(chǎn)出比最高的一步。一次臟數(shù)據(jù)帶來(lái)的模型性能下降可能比你換一個(gè)更強(qiáng)的backbone還要嚴(yán)重。如果你打算自采數(shù)據(jù)比如去果園或者菜市場(chǎng)拍真實(shí)的水果照片那就需要自己標(biāo)注。標(biāo)注的時(shí)候有兩個(gè)容易忽略的點(diǎn)。一是類(lèi)別的判定標(biāo)準(zhǔn)要寫(xiě)清楚比如“成熟”的定義是果皮底色達(dá)到某種色號(hào)、硬度計(jì)讀數(shù)在某范圍內(nèi)還是憑肉眼判斷必須在標(biāo)注規(guī)范里固化下來(lái)否則不同批次標(biāo)注的標(biāo)準(zhǔn)漂移會(huì)讓模型學(xué)到錯(cuò)誤邊界。二是同一個(gè)果實(shí)的照片不要同時(shí)分到訓(xùn)練集和驗(yàn)證集否則驗(yàn)證集就失去了意義后期評(píng)估指標(biāo)會(huì)虛高。2.3 數(shù)據(jù)增強(qiáng)讓模型學(xué)會(huì)看本質(zhì)而不是看背景水果成熟度識(shí)別里最容易出現(xiàn)的過(guò)擬合是模型學(xué)會(huì)了看背景而不是看果實(shí)。很多公開(kāi)數(shù)據(jù)集的拍攝背景高度一致要么全是果園的葉片要么全是實(shí)驗(yàn)室的白色臺(tái)面。這種情況下模型根本不需要認(rèn)真分析果實(shí)紋理只要檢測(cè)到畫(huà)面里有大量綠色就判定為“未成熟”驗(yàn)證集準(zhǔn)確率再高換個(gè)真實(shí)場(chǎng)景立馬現(xiàn)原形。對(duì)抗這個(gè)問(wèn)題數(shù)據(jù)增強(qiáng)比換模型更有效。我用過(guò)一套增強(qiáng)組合效果很穩(wěn)定隨機(jī)旋轉(zhuǎn)正負(fù)30度、隨機(jī)水平翻轉(zhuǎn)、隨機(jī)亮度對(duì)比度飽和度調(diào)整、隨機(jī)裁剪縮放再加上一種叫隨機(jī)擦除的操作——在圖片里隨機(jī)抹掉一塊矩形區(qū)域逼迫模型不能依賴(lài)某個(gè)局部特征來(lái)判斷。這套組合可以在不改變語(yǔ)義的前提下把訓(xùn)練集的有效樣本量擴(kuò)大一個(gè)數(shù)量級(jí)。# 核心數(shù)據(jù)增強(qiáng)配置基于torchvision的transforms實(shí)現(xiàn) import torchvision.transforms as T train_transforms T.Compose([ T.RandomResizedCrop(size(224, 224), scale(0.7, 1.0)), T.RandomHorizontalFlip(p0.5), T.RandomRotation(degrees30), T.ColorJitter(brightness0.3, contrast0.3, saturation0.3, hue0.05), T.RandomErasing(p0.5, scale(0.02, 0.15), ratio(0.3, 3.3)), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) valid_transforms T.Compose([ T.Resize((224, 224)), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])這套配置里有三個(gè)細(xì)節(jié)值得注意。RandomResizedCrop的scale參數(shù)設(shè)成0.7到1.0意思是裁剪區(qū)域占原圖面積的70%到100%這個(gè)比例既能模擬不同拍攝距離又不至于把果實(shí)主體裁掉。RandomRotation的角度范圍設(shè)到30度模擬的是手持拍攝時(shí)手機(jī)輕微傾斜的狀態(tài)。RandomErasing的擦除面積控制在2%到15%之間擦太大果實(shí)主體被破壞標(biāo)簽就不準(zhǔn)了擦太小起不到正則化效果。2.4 樣本不均衡和標(biāo)簽分布的處理思路水果成熟度數(shù)據(jù)集的類(lèi)別分布往往不是均勻的常見(jiàn)的情況是“成熟”樣本很多“過(guò)熟”和“生”樣本偏少。直接拿這種數(shù)據(jù)訓(xùn)練模型會(huì)對(duì)少數(shù)類(lèi)別識(shí)別能力很弱。我的處理習(xí)慣是先用類(lèi)別加權(quán)采樣讓每個(gè)batch里各類(lèi)別出現(xiàn)頻率盡量接近同時(shí)配合少量過(guò)采樣的方法。如果某個(gè)類(lèi)別實(shí)在太少可以對(duì)該類(lèi)圖片做更強(qiáng)的增強(qiáng)變換生成一個(gè)補(bǔ)充訓(xùn)練集。標(biāo)簽分布還有一個(gè)容易被忽視的細(xì)節(jié)——類(lèi)別順序。如果把“生、半熟、成熟、過(guò)熟”當(dāng)作一個(gè)有序標(biāo)簽序列但訓(xùn)練時(shí)用普通的交叉熵?fù)p失模型就丟掉了相鄰類(lèi)別之間的順序信息。一種改進(jìn)思路是對(duì)標(biāo)簽做軟編碼比如“八成熟”的樣本不再只給“成熟”這個(gè)硬標(biāo)簽而是同時(shí)給“接近成熟”一個(gè)較小的權(quán)重。這個(gè)思路在很多實(shí)際項(xiàng)目里都驗(yàn)證過(guò)有效實(shí)驗(yàn)部分可以作為亮點(diǎn)來(lái)寫(xiě)。3. 模型選型與實(shí)驗(yàn)方案設(shè)計(jì)3.1 用遷移學(xué)習(xí)還是從零訓(xùn)練直接說(shuō)結(jié)論這個(gè)問(wèn)題我在多個(gè)項(xiàng)目里反復(fù)驗(yàn)證過(guò)除非你有海量數(shù)據(jù)和充足算力否則用ImageNet預(yù)訓(xùn)練權(quán)重微調(diào)幾乎是唯一理性的選擇。水果成熟度識(shí)別的數(shù)據(jù)集規(guī)模通常在幾千張圖片的量級(jí)從零訓(xùn)練一個(gè)深度網(wǎng)絡(luò)很容易陷入嚴(yán)重的過(guò)擬合——訓(xùn)練集準(zhǔn)確率沖到99%驗(yàn)證集卻卡在70%上不去。而基于預(yù)訓(xùn)練模型做遷移學(xué)習(xí)相當(dāng)于讓模型先學(xué)會(huì)通用的視覺(jué)特征再用少量水果圖片微調(diào)最后的分類(lèi)層和部分卷積層收斂速度快、需要的樣本量小、泛化能力也好得多。選擇預(yù)訓(xùn)練模型時(shí)要破解一個(gè)常見(jiàn)的誤區(qū)模型不是越大越好。我用ResNet50、EfficientNet-B0和MobileNetV3做了對(duì)比實(shí)驗(yàn)結(jié)果很有意思。模型參數(shù)量驗(yàn)證集準(zhǔn)確率單張推理耗時(shí)CPU單張推理耗時(shí)GPUResNet50約25.6M92.3%約80ms約6msEfficientNet-B0約5.3M91.8%約65ms約5msMobileNetV3-Large約5.4M89.6%約45ms約3ms可以看到ResNet50和EfficientNet-B0的準(zhǔn)確率差距很小但參數(shù)量差了近5倍。你如果后續(xù)要部署到移動(dòng)端或者Web端做實(shí)時(shí)演示MobileNetV3的輕量?jī)?yōu)勢(shì)就很明顯了。我的建議是實(shí)驗(yàn)階段用ResNet50作為基線(xiàn)模型因?yàn)樗挠?xùn)練穩(wěn)定性好、調(diào)參資料最多作為對(duì)照基線(xiàn)最有說(shuō)服力如果時(shí)間和精力允許再跑一組EfficientNet-B0或者M(jìn)obileNetV3的對(duì)比實(shí)驗(yàn)論文里的“模型對(duì)比”章節(jié)就有了三組真實(shí)數(shù)據(jù)比只放一個(gè)模型強(qiáng)太多。3.2 分類(lèi)任務(wù)還是回歸任務(wù)這個(gè)選擇決定你的實(shí)驗(yàn)結(jié)構(gòu)水果成熟度識(shí)別有兩種任務(wù)定義方式理清這個(gè)區(qū)別你后面整個(gè)實(shí)驗(yàn)結(jié)構(gòu)就順了。第一種是分類(lèi)任務(wù)把成熟度分成若干離散等級(jí)比如生、半熟、成熟、過(guò)熟四類(lèi)用交叉熵?fù)p失訓(xùn)練。這種定義直觀易懂、評(píng)估指標(biāo)清晰適合本科畢設(shè)的體量。缺點(diǎn)是無(wú)法輸出連續(xù)值比如一張圖片判斷為“八成熟”無(wú)法再細(xì)分為“八點(diǎn)五成熟”。第二種是回歸任務(wù)直接預(yù)測(cè)一個(gè)連續(xù)值比如某參考體系下的成熟度評(píng)分0到1或者預(yù)測(cè)果實(shí)距離完全成熟的天數(shù)。這種定義更精細(xì)但難點(diǎn)在于需要有準(zhǔn)確連續(xù)標(biāo)注的訓(xùn)練數(shù)據(jù)。絕大多數(shù)公開(kāi)數(shù)據(jù)集只給離散等級(jí)標(biāo)簽想做回歸就得自己去補(bǔ)標(biāo)注工作量大不少。我實(shí)測(cè)下來(lái)的經(jīng)驗(yàn)是對(duì)畢設(shè)而言?xún)?yōu)先做分類(lèi)任務(wù)但在實(shí)驗(yàn)設(shè)計(jì)上把“分類(lèi)和回歸的對(duì)比”作為一個(gè)討論點(diǎn)寫(xiě)進(jìn)論文。你可以在分類(lèi)模型的基礎(chǔ)上把最后的Softmax換成單神經(jīng)元輸出用均方誤差損失微調(diào)看看連續(xù)預(yù)測(cè)的效果如何。這組對(duì)比實(shí)驗(yàn)的代碼改動(dòng)不到10行但在答辯時(shí)是一個(gè)很自然的加分點(diǎn)——它體現(xiàn)出你對(duì)問(wèn)題本質(zhì)的思考而不是只會(huì)在PyTorch里套模板。3.3 消融實(shí)驗(yàn)怎么做才有意義消融實(shí)驗(yàn)的結(jié)果直接決定你論文章節(jié)的說(shuō)服力。我建議至少設(shè)計(jì)四組對(duì)照實(shí)驗(yàn)第一組是基線(xiàn)實(shí)驗(yàn)用ResNet50加ImageViewNet預(yù)訓(xùn)練權(quán)重不做任何數(shù)據(jù)增強(qiáng)只看原始數(shù)據(jù)的表現(xiàn)第二組在基線(xiàn)上加完整數(shù)據(jù)增強(qiáng)驗(yàn)證增強(qiáng)策略對(duì)最終準(zhǔn)確率的影響第三組換成輕量級(jí)模型驗(yàn)證不同架構(gòu)在精度和速度上的權(quán)衡第四組在前三組最優(yōu)配置的基礎(chǔ)上對(duì)最后的全連接層結(jié)構(gòu)做一些調(diào)整比如加上一層Dropout。跑完四組實(shí)驗(yàn)后記錄下每組在驗(yàn)證集上的準(zhǔn)確率、精確率、召回率、F1分?jǐn)?shù)和推理耗時(shí)整理成表格放進(jìn)論文。這幾組實(shí)驗(yàn)的價(jià)值在于它們不是隨機(jī)試出來(lái)的每一組都在回答一個(gè)具體的問(wèn)題——數(shù)據(jù)增強(qiáng)到底有沒(méi)有用、輕量模型到底能不能打、全連接層的冗余參數(shù)會(huì)不會(huì)導(dǎo)致過(guò)擬合。這種“每個(gè)實(shí)驗(yàn)都有明確問(wèn)題指向”的設(shè)計(jì)邏輯是評(píng)審老師最認(rèn)可的實(shí)驗(yàn)規(guī)劃方式。4. 訓(xùn)練細(xì)節(jié)與調(diào)參心得從損失震蕩到穩(wěn)定收斂4.1 損失函數(shù)怎么選交叉熵不一定是最優(yōu)解絕大多數(shù)人會(huì)直接用交叉熵?fù)p失作為分類(lèi)任務(wù)的默認(rèn)選擇但我在水果成熟度項(xiàng)目里做對(duì)比實(shí)驗(yàn)時(shí)發(fā)現(xiàn)了一個(gè)值得注意的現(xiàn)象同一套數(shù)據(jù)、同一個(gè)模型把交叉熵?fù)Q成加了標(biāo)簽平滑的版本驗(yàn)證集準(zhǔn)確率能提升1到2個(gè)百分點(diǎn)。原因是標(biāo)簽平滑給正確標(biāo)簽的置信度打了折扣例如把one-hot編碼的1.0改成0.9余下的0.1攤到其他類(lèi)別上。這相當(dāng)于給模型注入了一定程度的“不確定性”容忍度恰好對(duì)沖了成熟度邊界模糊帶來(lái)的噪聲標(biāo)簽問(wèn)題。標(biāo)簽平滑的epsilon參數(shù)我一般設(shè)在0.1你可以試著在0.05到0.15之間調(diào)一下。設(shè)太大模型會(huì)欠擬合設(shè)太小效果不明顯。# PyTorch中實(shí)現(xiàn)帶標(biāo)簽平滑的交叉熵?fù)p失 import torch import torch.nn as nn import torch.nn.functional as F class LabelSmoothCrossEntropy(nn.Module): def __init__(self, num_classes, smoothing0.1): super().__init__() self.num_classes num_classes self.smoothing smoothing def forward(self, logits, targets): # 先構(gòu)造平滑后的真實(shí)分布 smoothed torch.full( size(targets.size(0), self.num_classes), fill_valueself.smoothing / (self.num_classes - 1) ) smoothed smoothed.to(targets.device) smoothed.scatter_(1, targets.unsqueeze(1), 1.0 - self.smoothing) # 計(jì)算KL散度作為損失 log_probs F.log_softmax(logits, dim1) loss F.kl_div(log_probs, smoothed, reductionbatchmean) return loss4.2 優(yōu)化器和學(xué)習(xí)率調(diào)度一個(gè)最容易踩坑的環(huán)節(jié)優(yōu)化器方面我推薦AdamW配合余弦退火學(xué)習(xí)率調(diào)度。這個(gè)組合相比SGD配合StepLR收斂速度更快、對(duì)初始學(xué)習(xí)率的敏感度更低。項(xiàng)目里我用的初始學(xué)習(xí)率是1e-4這是遷移學(xué)習(xí)微調(diào)場(chǎng)景下的常用起點(diǎn)——因?yàn)轭A(yù)訓(xùn)練權(quán)重已經(jīng)在一個(gè)大規(guī)模數(shù)據(jù)集上收斂過(guò)了過(guò)大的學(xué)習(xí)率會(huì)迅速破壞已經(jīng)學(xué)好的底層特征。學(xué)習(xí)率調(diào)度我試過(guò)兩種一種是按固定步長(zhǎng)衰減比如每10個(gè)epoch學(xué)習(xí)率乘以0.1另一種是余弦退火效果明顯更好。原因是余弦退火在訓(xùn)練后期學(xué)習(xí)率平滑下降模型有更多機(jī)會(huì)在loss曲面上的“窄谷”里找到更好的駐點(diǎn)。實(shí)際訓(xùn)練中我遇到過(guò)一種很典型的情況訓(xùn)練集loss持續(xù)下降驗(yàn)證集準(zhǔn)確率卻停滯不前換了余弦退火之后最后10個(gè)epoch里驗(yàn)證集準(zhǔn)確率還能再漲兩個(gè)點(diǎn)。這個(gè)現(xiàn)象值得寫(xiě)進(jìn)論文的“訓(xùn)練策略分析”部分。4.3 訓(xùn)練流程腳本一份可以直接復(fù)用的標(biāo)準(zhǔn)模板下面是一個(gè)精簡(jiǎn)但完整的訓(xùn)練腳本框架考慮到不同同學(xué)的環(huán)境差異里面去掉了和項(xiàng)目強(qiáng)相關(guān)的路徑硬編碼你直接把dataset部分替換成自己的數(shù)據(jù)加載邏輯就能跑通。# 訓(xùn)練流程框架遷移學(xué)習(xí) 標(biāo)簽平滑 余弦退火 import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import models from torchvision.datasets import ImageFolder from torchvision import transforms # 數(shù)據(jù)路徑按自己的目錄結(jié)構(gòu)修改 train_dir data/fruit_maturity/train valid_dir data/fruit_maturity/valid # 數(shù)據(jù)增強(qiáng)和歸一化沿用第2節(jié)的配置 train_transforms transforms.Compose([ transforms.RandomResizedCrop((224, 224), scale(0.7, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees30), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3), transforms.RandomErasing(p0.5, scale(0.02, 0.15)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) valid_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 ImageFolder(train_dir, transformtrain_transforms) valid_dataset ImageFolder(valid_dir, transformvalid_transforms) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) valid_loader DataLoader(valid_dataset, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue) # 加載預(yù)訓(xùn)練ResNet50并替換分類(lèi)頭 model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) num_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(p0.3), nn.Linear(num_features, len(train_dataset.classes)) ) # 遷移學(xué)習(xí)關(guān)鍵凍結(jié)除最后兩層的全部參數(shù) for name, param in model.named_parameters(): if layer4 not in name and fc not in name: param.requires_grad False device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion LabelSmoothCrossEntropy( num_classeslen(train_dataset.classes), smoothing0.1 ) optimizer torch.optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr1e-4, weight_decay1e-5 ) scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max50, eta_min1e-6 ) # 訓(xùn)練循環(huán)精簡(jiǎn)版早停等邏輯可按需補(bǔ)全 for epoch in range(50): 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() * images.size(0) model.eval() correct, total 0, 0 with torch.no_grad(): for images, labels in valid_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() scheduler.step() current_lr scheduler.get_last_lr()[0] train_acc 100 * correct / total print(fEpoch {epoch1:02d} | Loss {running_loss/len(train_loader):.4f} f| Val Acc {train_acc:.2f}% | LR {current_lr:.2e})腳本里有幾個(gè)設(shè)計(jì)值得展開(kāi)說(shuō)一下。第一我先凍結(jié)了除layer4和fc以外的所有層參數(shù)這種“分區(qū)微調(diào)”策略讓訓(xùn)練初期的參數(shù)更新集中在新分類(lèi)頭上等它學(xué)到一定程度再解凍更多層通常能帶來(lái)更穩(wěn)定的收斂過(guò)程。第二個(gè)是weight_decay設(shè)到1e-5這是針對(duì)AdamW經(jīng)驗(yàn)值。第三是patience上有跟loss或者驗(yàn)證準(zhǔn)確率掛鉤的早停機(jī)制本項(xiàng)目里我加了一個(gè)連續(xù)8個(gè)epoch驗(yàn)證集準(zhǔn)確率沒(méi)有刷新就停止訓(xùn)練的邏輯防止后期過(guò)擬合浪費(fèi)算力。4.4 數(shù)據(jù)量少的情況下怎么把性能再擠一擠公開(kāi)數(shù)據(jù)集圖片量有限是最常見(jiàn)的問(wèn)題我見(jiàn)過(guò)不少同學(xué)在只有幾百?gòu)垐D片的數(shù)據(jù)集上硬跑深度模型結(jié)果自然不理想。這種情況下有幾種有效的補(bǔ)救手段。第一是更強(qiáng)力的擴(kuò)充策略除了前面提到的變換還可以試試MixUp即把兩張圖片按一定比例混合并讓標(biāo)簽也按同樣比例混合。第二是先從預(yù)訓(xùn)練模型中提取特征只訓(xùn)練最后的分類(lèi)層這樣模型需要學(xué)習(xí)的參數(shù)數(shù)量從千萬(wàn)級(jí)降到幾千幾乎不會(huì)過(guò)擬合。第三是引入輔助訓(xùn)練集比如在大量未標(biāo)注的果蔬圖片上做一個(gè)自監(jiān)督預(yù)訓(xùn)練再用少量標(biāo)注數(shù)據(jù)微調(diào)。5. 界面交互與部署讓畢設(shè)的演示效果提升一個(gè)檔次5.1 用Gradio快速封裝一個(gè)可交互的Demo畢設(shè)答辯時(shí)要給評(píng)審老師做現(xiàn)場(chǎng)演示光在終端里打印預(yù)測(cè)結(jié)果觀感差很多。強(qiáng)烈建議你用Gradio或Streamlit封裝一個(gè)Web界面上傳一張水果圖片在界面上直接顯示預(yù)測(cè)類(lèi)別和置信度復(fù)雜度低且效果直觀。Gradio的安裝和啟動(dòng)代碼量非常小我實(shí)際用到的核心代碼不到20行。# 基于Gradio的水果成熟度識(shí)別演示界面 import gradio as gr import torch from torchvision import transforms from PIL import Image # 這里假設(shè)你已經(jīng)用訓(xùn)練好的權(quán)重加載好模型 model torch.load(best_model.pth, map_locationcpu) model.eval() class_names [生果, 半熟, 成熟, 過(guò)熟] def predict(image): # 預(yù)處理邏輯要得到與訓(xùn)練時(shí)完全一致 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]) ]) tensor transform(image).unsqueeze(0) with torch.no_grad(): outputs model(tensor) probs torch.softmax(outputs, dim1)[0] return {class_names[i]: float(probs[i]) for i in range(len(class_names))} demo gr.Interface( fnpredict, inputsgr.Image(typepil), outputsgr.Label(num_top_classes4), title水果成熟度識(shí)別系統(tǒng), description上傳一張水果圖片模型將預(yù)測(cè)其成熟度等級(jí) ) demo.launch()注意model torch.load(best_model.pth, map_locationcpu)這行實(shí)際使用時(shí)會(huì)涉及到完整的模型權(quán)重保存和加載方式建議在訓(xùn)練腳本里把訓(xùn)練好的模型權(quán)重單獨(dú)保存為state_dict加載時(shí)再重建模型結(jié)構(gòu)這樣最穩(wěn)妥不易出錯(cuò)。5.2 邊緣端部署的思路如果你想再進(jìn)一步把這個(gè)項(xiàng)目包裝成“軟硬結(jié)合”的畢設(shè)可以試試把模型導(dǎo)出為ONNX格式用ONNX Runtime在CPU上推理。這一步能把模型的推理速度和通用性都提升不少而且不依賴(lài)PyTorch環(huán)境。# 將PyTorch模型導(dǎo)出為ONNX格式 import torch from torchvision import models model models.resnet50() model.fc torch.nn.Linear(model.fc.in_features, 4) model.load_state_dict(torch.load(best_state_dict.pth)) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, fruit_maturity.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}, opset_version11 )導(dǎo)出時(shí)dynamic_axes這個(gè)參數(shù)建議保留它允許推理時(shí)batch大小不固定方便后續(xù)兼容不同批次的輸入。做Android端部署的話(huà)可以基于ONNX Runtime Mobile進(jìn)一步壓縮和優(yōu)化我見(jiàn)過(guò)有同學(xué)把整個(gè)模型打到一個(gè)移動(dòng)端App里在手機(jī)攝像頭上做實(shí)時(shí)檢測(cè)答辯時(shí)演示效果非常好。6. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄6.1 驗(yàn)證集準(zhǔn)確率很高測(cè)試集卻明顯偏低這是我在這個(gè)項(xiàng)目里遇到最多的問(wèn)題。排查下來(lái)根本原因通常是數(shù)據(jù)劃分方式不嚴(yán)謹(jǐn)。如果你在劃分訓(xùn)練集和驗(yàn)證集時(shí)沒(méi)有考慮同一個(gè)來(lái)源或同一批拍的圖片會(huì)被同時(shí)分到兩邊那模型實(shí)際是在“記住”特定拍攝環(huán)境下的風(fēng)格而不是在“學(xué)會(huì)”識(shí)別成熟度。解決方法是按圖片的采集批次或來(lái)源分組劃分保證同一組的圖片只出現(xiàn)在訓(xùn)練集或驗(yàn)證集之一。6.2 模型把“過(guò)熟”和“腐爛”混為一談這個(gè)問(wèn)題的根源在于標(biāo)簽定義不夠細(xì)。很多數(shù)據(jù)集的“過(guò)熟”標(biāo)注者在拍圖時(shí)會(huì)順手拍一些已經(jīng)開(kāi)始腐爛的果實(shí)進(jìn)去但實(shí)際業(yè)務(wù)中過(guò)熟和腐爛可能對(duì)應(yīng)完全不同的處理路徑——過(guò)熟可能還能做果汁腐爛只能扔掉。遇到這種情況不要急著改模型先回去整理數(shù)據(jù)。要么把這類(lèi)樣本單獨(dú)剔出來(lái)要么干脆新增一個(gè)“腐爛”類(lèi)別讓模型在特征空間里學(xué)得更干凈。6.3 訓(xùn)練曲線(xiàn)平穩(wěn)但驗(yàn)證集準(zhǔn)確率一直卡在70%左右卡在70%上不去在四分類(lèi)問(wèn)題上通常是模型根本沒(méi)有學(xué)到有效特征。先檢查是不是在遷移學(xué)習(xí)時(shí)把所有參數(shù)都凍結(jié)了只剩最后一層如果是這樣前半程可以但光靠最后一層線(xiàn)性分類(lèi)在復(fù)雜特征上是有限的建議解凍最后兩到三層再做聯(lián)合微調(diào)。另一個(gè)排查方向是學(xué)習(xí)率如果設(shè)到1e-3甚至更高預(yù)訓(xùn)練權(quán)重可能已經(jīng)被破壞這時(shí)候把學(xué)習(xí)率降到1e-4或者1e-5重新訓(xùn)練一輪。6.4 一幅圖上出現(xiàn)多個(gè)果實(shí)判斷結(jié)果不穩(wěn)定這個(gè)問(wèn)題很常見(jiàn)——你采集的圖片往往包含多個(gè)果實(shí)有的熟了有的沒(méi)熟模型就不知道以誰(shuí)為判斷依據(jù)。我給的解決方案是在數(shù)據(jù)預(yù)處理階段做一個(gè)簡(jiǎn)單的果實(shí)檢測(cè)裁剪比如先用成熟的檢測(cè)模型把圖中最大的果實(shí)區(qū)域框出來(lái)裁出來(lái)單獨(dú)判斷成熟度。這個(gè)方案本身就可以作為論文里的一個(gè)優(yōu)化點(diǎn)來(lái)寫(xiě)。7. 論文寫(xiě)作與答辯加分項(xiàng)7.1 論文框架怎么搭圍繞這個(gè)選題論文結(jié)構(gòu)我建議按七章來(lái)組織第一章緒論重點(diǎn)是講清楚水果成熟度識(shí)別在果蔬分揀、品質(zhì)檢測(cè)場(chǎng)景下的應(yīng)用價(jià)值以及為什么要用深度學(xué)習(xí)方法替代傳統(tǒng)人工判斷第二章相關(guān)工作綜述傳統(tǒng)的顏色特征、紋理特征判斷方法和深度學(xué)習(xí)方法在農(nóng)產(chǎn)品品質(zhì)檢測(cè)中的進(jìn)展第三章數(shù)據(jù)集構(gòu)建與預(yù)處理把數(shù)據(jù)來(lái)源、標(biāo)注規(guī)范、增強(qiáng)策略寫(xiě)清楚第四章基于深度卷積網(wǎng)絡(luò)的成熟度識(shí)別方法詳細(xì)說(shuō)明模型結(jié)構(gòu)、損失函數(shù)和訓(xùn)練策略第五章實(shí)驗(yàn)與結(jié)果分析放四組消融實(shí)驗(yàn)的結(jié)果和對(duì)比表格第六章系統(tǒng)實(shí)現(xiàn)與界面設(shè)計(jì)展示W(wǎng)eb演示系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié)第七章總結(jié)與展望。7.2 答辯時(shí)怎么講才能讓評(píng)審記住你的工作答辯展示的重點(diǎn)不是“我用了ResNet50”而是“我為什么用ResNet50、我通過(guò)哪些實(shí)驗(yàn)證明這個(gè)選擇是合理的”。我見(jiàn)過(guò)不少同學(xué)在答辯時(shí)被追問(wèn)“你為什么不用Transformer”“你為什么不用更復(fù)雜的模型”如果你沒(méi)有一根清晰的邏輯線(xiàn)很容易被問(wèn)住。我的建議是提前準(zhǔn)備三個(gè)層面的回答。第一層是基線(xiàn)層面ResNet50作為卷積網(wǎng)絡(luò)經(jīng)典架構(gòu)在中等規(guī)模數(shù)據(jù)集上訓(xùn)練穩(wěn)定適合作為基線(xiàn)模型第二層是對(duì)比層面我另外跑了EfficientNet-B0和MobileNetV3的對(duì)比實(shí)驗(yàn)證明輕量模型在精度接近的情況下速度更快為部署端選型提供依據(jù)第三層是問(wèn)題層面這個(gè)任務(wù)的核心難點(diǎn)不是模型表達(dá)能力不夠而是類(lèi)別邊界模糊和樣本不均衡我的消融實(shí)驗(yàn)證明數(shù)據(jù)增強(qiáng)和標(biāo)簽平滑帶來(lái)的收益比換一個(gè)更大的模型更明顯。能把這三點(diǎn)講清楚答辯的“為什么”問(wèn)題基本都能從容應(yīng)對(duì)。8. 后續(xù)擴(kuò)展從畢設(shè)到項(xiàng)目成果的幾步延伸如果拿到題目之后還有富余時(shí)間或者論文想要沖刺更高評(píng)價(jià)有幾種擴(kuò)展值得考慮。第一種是做一個(gè)果實(shí)檢測(cè)與成熟度識(shí)別的串聯(lián)系統(tǒng)先用目標(biāo)檢測(cè)模型定位果實(shí)再對(duì)每個(gè)果實(shí)單獨(dú)判斷成熟度這更接近實(shí)際分揀場(chǎng)景第二種是回歸化改造用成熟度評(píng)分替代離散標(biāo)簽比離散分類(lèi)更精細(xì)第三種是加入多光譜信息比如用近紅外圖像輔助判斷內(nèi)部糖度預(yù)處理復(fù)雜度和數(shù)據(jù)采集門(mén)檻較高但應(yīng)用價(jià)值更強(qiáng)。我個(gè)人的體會(huì)是水果成熟度識(shí)別這個(gè)題目最好的地方在于它的“兜底能力”。哪怕前期準(zhǔn)備不充分、數(shù)據(jù)質(zhì)量一般用遷移學(xué)習(xí)和合理的數(shù)據(jù)增強(qiáng)仍然能跑出一個(gè)不錯(cuò)的基線(xiàn)結(jié)果反過(guò)來(lái)如果你愿意花時(shí)間把數(shù)據(jù)搞干凈、把消融實(shí)驗(yàn)做完整、把部署界面做出來(lái)它又能支撐起一個(gè)相當(dāng)豐滿(mǎn)的畢設(shè)故事。對(duì)很多第一次完整跑完一個(gè)深度學(xué)習(xí)項(xiàng)目的同學(xué)來(lái)說(shuō)這種“下限不低、上限很高”的題目其實(shí)比那些聽(tīng)起來(lái)高大上但實(shí)驗(yàn)條件不成熟的課題靠譜得多。如果你現(xiàn)在正準(zhǔn)備開(kāi)始動(dòng)手我的建議是先花兩天時(shí)間把數(shù)據(jù)整理干凈把標(biāo)注規(guī)范寫(xiě)清楚這個(gè)環(huán)節(jié)做好后面所有實(shí)驗(yàn)都會(huì)順很多。等訓(xùn)練腳本跑通之后再一步步加入增強(qiáng)策略、標(biāo)簽平滑、模型對(duì)比你的知識(shí)體系和最終成果就是這樣一層一層長(zhǎng)出來(lái)的。