別:從數(shù)據(jù)準(zhǔn)備到模型調(diào)優(yōu)全攻略)
開頭如果你正在準(zhǔn)備課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)手頭分到的題目恰好是“基于機(jī)器學(xué)習(xí)的CNN卷積神經(jīng)網(wǎng)絡(luò)對海洋殼類生物識(shí)別”你大概率會(huì)經(jīng)歷這樣一段心路歷程先覺得題目挺有意思再發(fā)現(xiàn)公開數(shù)據(jù)集不好找然后糾結(jié)于網(wǎng)絡(luò)結(jié)構(gòu)到底怎么選最后被訓(xùn)練時(shí)的過擬合和類別不平衡折磨到懷疑人生。這個(gè)題目我前后完整做過兩輪一次是幫某高校的同學(xué)調(diào)代碼一次是自己重新跑通了整個(gè)流程中間踩過的坑確實(shí)不少。這篇文章就把整個(gè)項(xiàng)目的完整鏈路拆開講清楚從數(shù)據(jù)集準(zhǔn)備、網(wǎng)絡(luò)選型、訓(xùn)練調(diào)參到最后的評估分析和常見問題排查每個(gè)環(huán)節(jié)我都會(huì)給出可以直接照抄的做法和參數(shù)也會(huì)解釋背后的原理。無論你是剛接觸深度學(xué)習(xí)的小白還是已經(jīng)跑過幾個(gè)分類項(xiàng)目的同學(xué)這篇文章都能幫你少走彎路把項(xiàng)目做成答辯時(shí)拿得出手的樣子。1. 為什么殼類識(shí)別適合用CNN來做——項(xiàng)目思路拆解1.1 題目的本質(zhì)是什么先把這個(gè)題目拆開看?!盎跈C(jī)器學(xué)習(xí)的CNN卷積神經(jīng)網(wǎng)絡(luò)”這句話說出來有點(diǎn)繞但本質(zhì)就一句話用卷積神經(jīng)網(wǎng)絡(luò)對圖像進(jìn)行分類把一張包含海洋殼類生物的圖片識(shí)別成正確的類別比如扇貝、牡蠣、蛤蜊、花甲、貽貝、海螺、鮑魚等等。為什么導(dǎo)師愛出這類題目因?yàn)樗诩夹g(shù)難度和應(yīng)用價(jià)值之間找到了一個(gè)很好的平衡點(diǎn)。海洋殼類生物的識(shí)別在漁業(yè)資源統(tǒng)計(jì)、海洋生態(tài)監(jiān)測、水產(chǎn)養(yǎng)殖管理、海鮮市場自動(dòng)結(jié)算等場景里都有真實(shí)需求。如果純靠人工識(shí)別既費(fèi)時(shí)又容易疲勞出錯(cuò)尤其是初學(xué)者面對幾十種外觀相似的貝殼時(shí)常常分不清花甲和蛤蜊、牡蠣和扇貝的細(xì)微差別。而CNN恰好擅長從圖像中自動(dòng)提取紋理、邊緣、形狀、顏色分布這些特征把它作為課題來做既能體現(xiàn)工程能力又有明確的落地場景答辯的時(shí)候也容易講清楚“你為什么要做這件事”。1.2 為什么是CNN而不是傳統(tǒng)方法很多課程設(shè)計(jì)題目里既寫了“機(jī)器學(xué)習(xí)”又寫了“CNN”有些同學(xué)會(huì)困惑傳統(tǒng)機(jī)器學(xué)習(xí)不是也做圖像識(shí)別嗎比如用HOG特征SVM或者顏色直方圖隨機(jī)森林為什么非得用CNN我從實(shí)際效果出發(fā)說一個(gè)樸素的結(jié)論傳統(tǒng)方法在簡單場景下確實(shí)可行比如純色背景下的單品種分類但一旦背景復(fù)雜、光照變化大、物體之間有遮擋傳統(tǒng)方法提取的“手工特征”就非常脆弱。想象一下你要區(qū)分兩種顏色紋理特別接近的貝殼HOG特征抓的是梯度方向如果兩種貝殼輪廓相近但花紋不同HOG基本就沒區(qū)分度了顏色直方圖更不靠譜同一種貝類在不同海域、不同光照下拍出來的顏色差異可能比不同種類之間的差異還大。CNN的核心優(yōu)勢是“端到端”學(xué)習(xí)——它不靠人來設(shè)計(jì)特征而是通過卷積核自動(dòng)從數(shù)據(jù)中學(xué)習(xí)特征。淺層卷積核學(xué)到的可能是邊緣、角點(diǎn)、顏色斑塊深層卷積核學(xué)到的就是“這種紋理屬于什么貝殼”這類語義特征。這就像一個(gè)經(jīng)驗(yàn)豐富的漁民他看一個(gè)貝殼不是只看顏色而是看形狀、紋路、光澤、厚度這些綜合信息CNN就是在模擬這種綜合判斷能力。所以這個(gè)題目選CNN本質(zhì)上不是在“趕時(shí)髦”而是圖像分類任務(wù)里最合理的方案。1.3 完整技術(shù)路線總覽整個(gè)項(xiàng)目的技術(shù)路線可以概括為五個(gè)環(huán)節(jié)采集與整理數(shù)據(jù)集、圖像預(yù)處理與增強(qiáng)、搭建CNN模型、訓(xùn)練與調(diào)參、評估與結(jié)果分析。聽起來很常規(guī)但每個(gè)環(huán)節(jié)在這個(gè)具體題目下都有它的特殊性。比如說數(shù)據(jù)集環(huán)節(jié)貓狗分類可以直接下載現(xiàn)成的數(shù)據(jù)集但“海洋殼類生物”沒有一個(gè)非常統(tǒng)一、帶好標(biāo)注的公開數(shù)據(jù)集你更可能需要自己從多個(gè)來源收集、清洗、標(biāo)注這本身占掉整個(gè)項(xiàng)目約一半的工作量。再比如模型環(huán)節(jié)貝殼類圖像的類別之間相似度高、類內(nèi)差異也不小同一品種可能從小長到大外形差異很大這就要求模型具備足夠的特征提取能力但你的訓(xùn)練數(shù)據(jù)量又可能不足以支撐一個(gè)特別深的網(wǎng)絡(luò)所以怎么在“容量”和“數(shù)據(jù)”之間取得平衡是貫穿始終的核心矛盾。還有一點(diǎn)容易被忽視評估環(huán)節(jié)。這個(gè)題目的類別往往是不均衡的。有些種類圖片多、有些種類圖片特別少這時(shí)候光看準(zhǔn)確率會(huì)掩蓋很多問題必須結(jié)合混淆矩陣、精準(zhǔn)率、召回率、F1分?jǐn)?shù)做綜合分析。這些我會(huì)在后面的章節(jié)逐一展開。2. 數(shù)據(jù)準(zhǔn)備階段最容易踩的坑——從采集到增強(qiáng)2.1 數(shù)據(jù)從哪來先解決最頭疼的問題海洋殼類生物的數(shù)據(jù)集從哪里找。我試過幾條路分別說下感受。第一條路是公開數(shù)據(jù)集。像ShellImageNet、一些海洋生物識(shí)別競賽的數(shù)據(jù)集可以試試但要做好心理準(zhǔn)備。有的數(shù)據(jù)集只包含少數(shù)幾個(gè)大類有的標(biāo)注質(zhì)量參差不齊一張圖上可能有多只貝殼只有其中一個(gè)被框出來作為標(biāo)簽。如果你不仔細(xì)清洗模型學(xué)到的就是“圖片中有貝殼”而非“圖片中的主要物體是A類”這對分類任務(wù)來說是致命的。第二條路是網(wǎng)絡(luò)圖片抓取。比如從圖庫網(wǎng)站按關(guān)鍵詞下載“扇貝”“牡蠣”“花甲”等圖片這條路速度快但很考驗(yàn)?zāi)愕那逑茨芰ΑK阉饕婺芩训酱罅慨嬞|(zhì)模糊、角度刁鉆、甚至混入無關(guān)內(nèi)容的圖片一張一張篩會(huì)很痛苦但不篩的后果更痛苦——模型會(huì)把背景當(dāng)特征學(xué)。第三條路是自行拍攝。如果條件允許去海鮮市場拍是效果最好的方式可以控制背景、光照、拍攝角度。當(dāng)年某高校有同學(xué)就是帶著手機(jī)去市場蹲了一下午拍了兩千多張數(shù)據(jù)質(zhì)量比網(wǎng)上找的高出一大截。但這種方式耗時(shí)耗力對大多數(shù)人來說只適合作為補(bǔ)充。綜合來看我的建議是以公開數(shù)據(jù)集或網(wǎng)絡(luò)圖片為基礎(chǔ)用自行拍攝補(bǔ)充弱勢類別然后花時(shí)間做一輪嚴(yán)格的人工清洗。清洗的標(biāo)準(zhǔn)就一條圖片主體是否清晰、單一、符合類別標(biāo)簽。模糊的、多只混在一起的、只有部分殼體的寧可刪掉也不要留。提示數(shù)據(jù)質(zhì)量永遠(yuǎn)比數(shù)據(jù)數(shù)量重要。100張清晰的扇貝特寫效果大概率好過300張背景雜亂、內(nèi)容含糊的扇貝圖片。2.2 標(biāo)注與類別的確定類別數(shù)量這個(gè)問題課程設(shè)計(jì)一般做到8到15類比較合適太多了數(shù)據(jù)收集工作量大模型分類難度也高太少了又顯得工作量不足。我當(dāng)時(shí)用的是10類扇貝、牡蠣、蛤蜊、花甲、貽貝、海螺、鮑魚、毛蚶、蟶子、螺螄。確定好類別后要注意確保每張圖都確實(shí)是“單一類別主體”。最好把所有圖片按類別建好文件夾文件夾名就是類別名然后用腳本做二次校驗(yàn)。我還會(huì)統(tǒng)計(jì)每個(gè)類別的圖片數(shù)量選一個(gè)數(shù)量最少的類別作為基準(zhǔn)保證各類別數(shù)量差距控制在3倍以內(nèi)。比如某個(gè)類別只有150張其他類別最好不要超過450張否則類別不平衡的問題會(huì)很嚴(yán)重后面訓(xùn)練和評估都會(huì)很痛苦。2.3 圖像預(yù)處理細(xì)節(jié)預(yù)處理這一步看起來簡單但直接影響訓(xùn)練效果。以下是我實(shí)踐下來比較穩(wěn)的一套流程圖片尺寸統(tǒng)一縮放到224x224這是多數(shù)預(yù)訓(xùn)練模型的默認(rèn)輸入尺寸??s放時(shí)注意比例變形的問題——貝殼類物體是長條形的直接拉伸會(huì)把形狀比例搞亂比如蟶子本來是細(xì)長的拉伸成正方形看起來就像另一個(gè)物種。我的做法是先等比縮放讓短邊到達(dá)224然后從中心裁剪出224x224的區(qū)域。這樣做會(huì)損失一點(diǎn)邊緣信息但對大多數(shù)圖片來說主體都集中在中心區(qū)域影響不大。歸一化也很關(guān)鍵。PyTorch里如果用ImageNet預(yù)訓(xùn)練模型就要按ImageNet的均值和標(biāo)準(zhǔn)差做標(biāo)準(zhǔn)化這些數(shù)值是固定的直接用就行。不用預(yù)訓(xùn)練模型的話可以用自己數(shù)據(jù)集的均值和標(biāo)準(zhǔn)差但千萬不能跳過這一步否則輸入像素值范圍不統(tǒng)一網(wǎng)絡(luò)收斂會(huì)明顯變慢。還有個(gè)細(xì)節(jié)值得提數(shù)據(jù)集的劃分要按類別分層隨機(jī)劃分不能簡單全體隨機(jī)。因?yàn)槟愕牟牧蟻碓磸?fù)雜同一個(gè)來源的圖片可能在背景、色調(diào)上高度相似如果不分層劃分可能某一類圖片只在訓(xùn)練集里出現(xiàn)驗(yàn)證集和測試集里該類別占比失衡評估結(jié)果就會(huì)失真。一般建議訓(xùn)練集70%驗(yàn)證集15%測試集15%并且固定隨機(jī)種子保證實(shí)驗(yàn)可復(fù)現(xiàn)。2.4 數(shù)據(jù)增強(qiáng)怎么做不過度數(shù)據(jù)增強(qiáng)本質(zhì)上是“故意制造更多訓(xùn)練樣本”。貝殼識(shí)別這個(gè)場景增強(qiáng)策略比一般自然圖像分類要保守一些因?yàn)樵鰪?qiáng)太狠會(huì)破壞物種特征。我踩過的一個(gè)坑是把旋轉(zhuǎn)角度設(shè)得過大結(jié)果海螺旋轉(zhuǎn)180度之后從外觀上看起來和另外一個(gè)類別極其相似模型直接學(xué)出了“混淆特征”。我的推薦組合是這樣的隨機(jī)水平翻轉(zhuǎn)概率0.5隨機(jī)旋轉(zhuǎn)幅度在[-15度, 15度]之間隨機(jī)亮度/對比度/飽和度調(diào)整幅度在0.8到1.2之間隨機(jī)縮放裁剪這個(gè)用PyTorch的RandomResizedCropscale范圍設(shè)0.8到1.0再加一個(gè)RandomAffine的輕微平移。不要用太激進(jìn)的色彩抖動(dòng)。有些同學(xué)喜歡把hue值拉得很大結(jié)果花甲原本灰色的殼被增強(qiáng)成彩色這種失真樣本反而干擾學(xué)習(xí)。記住增強(qiáng)的目的是模擬現(xiàn)實(shí)拍攝中的合理變化不是制造科幻圖片。訓(xùn)練時(shí)如果發(fā)現(xiàn)增強(qiáng)后的圖片“人眼都認(rèn)不出來”那這個(gè)增強(qiáng)策略就要調(diào)整。3. CNN網(wǎng)絡(luò)結(jié)構(gòu)怎么選——從零訓(xùn)練還是遷移學(xué)習(xí)3.1 經(jīng)典CNN結(jié)構(gòu)是如何工作的在動(dòng)手選網(wǎng)絡(luò)之前先把CNN的基本結(jié)構(gòu)理清楚。一個(gè)典型的圖像分類CNN由三個(gè)部分組成卷積層負(fù)責(zé)特征提取池化層負(fù)責(zé)特征壓縮全連接層負(fù)責(zé)最終分類決策。卷積層可以理解成“拿著一個(gè)放大鏡在圖片上滑動(dòng)”每次看到一小塊區(qū)域提取這塊區(qū)域的局部模式。多個(gè)卷積核疊加就相當(dāng)于用不同角度去觀察同一塊區(qū)域有的關(guān)注邊緣有的關(guān)注紋理有的關(guān)注顏色分布。這些信息一層層往上傳遞低層的微觀特征逐漸組合成高層的語義特征最后全連接層根據(jù)這些語義特征輸出每個(gè)類別的概率。池化層的作用是“降維但保關(guān)鍵信息”它把一大塊區(qū)域匯總成一個(gè)元素既減少了計(jì)算量又讓模型對物體的位置變化不那么敏感。比如扇貝無論出現(xiàn)在圖片左側(cè)還是右側(cè)經(jīng)過池化后它的特征表達(dá)都能基本保持一致。理解這些對項(xiàng)目有什么實(shí)際幫助當(dāng)你發(fā)現(xiàn)模型識(shí)別不準(zhǔn)某類貝殼時(shí)你需要判斷是特征提取能力不足還是分類決策有問題。前者可能要換更深的網(wǎng)絡(luò)或增加卷積核數(shù)量后者可能需要調(diào)整全連接層結(jié)構(gòu)或加Dropout。如果不懂底層結(jié)構(gòu)就只能瞎試參數(shù)效率非常低。3.2 從零訓(xùn)練還是用預(yù)訓(xùn)練模型這是整個(gè)項(xiàng)目中最需要做決策的一個(gè)點(diǎn)。從零訓(xùn)練一個(gè)網(wǎng)絡(luò)意味著所有參數(shù)都從隨機(jī)初始化開始學(xué)遷移學(xué)習(xí)則是加載一個(gè)在ImageNet上預(yù)訓(xùn)練好的模型把它的特征提取能力借過來只針對我們的任務(wù)“微調(diào)”。對于這個(gè)題目我強(qiáng)烈推薦遷移學(xué)習(xí)除非你有特別充分的理由比如要求完全手寫網(wǎng)絡(luò)結(jié)構(gòu)。原因很簡單數(shù)據(jù)量不夠。ImageNet上訓(xùn)練過的模型底層卷積核已經(jīng)學(xué)會(huì)了豐富的通用特征——邊緣、紋理、顏色漸變等等。貝殼的識(shí)別很大程度上依賴這些底層特征所以即便Shellfish數(shù)據(jù)本身量不大底層特征也能“復(fù)用”。你只需要讓模型在高層特征上適配貝殼任務(wù)就行。用ImageNet預(yù)訓(xùn)練模型一般效果能比從零訓(xùn)練高出10到20個(gè)百分點(diǎn)這在答辯中是質(zhì)的差別。當(dāng)然如果學(xué)校要求必須手寫網(wǎng)絡(luò)結(jié)構(gòu)那也不是不行。我后面會(huì)給出一個(gè)能用的自定義小模型方案。3.3 常用模型選型對比與推薦如果決定用遷移學(xué)習(xí)可選模型很多。我把主流模型在這個(gè)任務(wù)上的表現(xiàn)梳理一下。ResNet18是最輕量級的選擇參數(shù)量約1120萬訓(xùn)練速度快對顯存要求低特征提取能力對于貝殼分類這種中等難度任務(wù)完全夠用。缺點(diǎn)是準(zhǔn)確率上限略低于更深模型。ResNet50更穩(wěn)妥殘差連接讓深度網(wǎng)絡(luò)不那么容易梯度消失訓(xùn)練效果通常優(yōu)于ResNet18但顯存和訓(xùn)練時(shí)間也相應(yīng)增加。MobileNetV3是輕量化設(shè)計(jì)的典型計(jì)算量非常小如果之后想把模型部署到手機(jī)端或嵌入式設(shè)備上它是最佳選項(xiàng)。準(zhǔn)確率在數(shù)據(jù)集不是特別難的情況下表現(xiàn)也不錯(cuò)基本是“性價(jià)比之王”。EfficientNet系列則在參數(shù)效率和精度之間取得了很好的平衡EfficientNet-B0就能和ResNet50打得有來有回但它的輸入預(yù)處理有點(diǎn)特殊需要按照官方推薦設(shè)置縮放和歸一化參數(shù)。我的建議是如果你的訓(xùn)練設(shè)備顯卡顯存不超過6GB優(yōu)先用ResNet18或MobileNetV3顯存在8GB以上直接上ResNet50想追求更極致的精度再試EfficientNet-B0但要注意它訓(xùn)練收斂稍微慢一些。3.4 自定義小模型的可行性方案萬一必須從零手寫結(jié)構(gòu)我提供一個(gè)我自己實(shí)測過、可以在小數(shù)據(jù)集上收斂的輕量網(wǎng)絡(luò)配置。這個(gè)方案是“三卷積兩全連結(jié)構(gòu)”第一個(gè)卷積層32個(gè)卷積核卷積核大小為3x3接ReLU和2x2最大池化第二個(gè)卷積層64個(gè)卷積核同樣3x3接ReLU和池化第三個(gè)卷積層128個(gè)卷積核再接池化然后拉平接一個(gè)256維的全連接層最后接類別數(shù)的輸出層。每個(gè)卷積層中間加上BatchNorm全連接層之間加Dropout(0.5)。這套結(jié)構(gòu)總參數(shù)量大約幾十萬級別相比動(dòng)輒上千萬的預(yù)訓(xùn)練模型輕得多在幾百張圖片的數(shù)據(jù)集上有能力收斂。但實(shí)事求是地講它的準(zhǔn)確率上限確實(shí)不如預(yù)訓(xùn)練模型一般能跑到80%到85%左右就不錯(cuò)了。如果答辯時(shí)允許對比實(shí)驗(yàn)可以把“自定義小模型”和“預(yù)訓(xùn)練模型”做一個(gè)消融對比證明遷移學(xué)習(xí)的優(yōu)勢這反而是一個(gè)加分項(xiàng)。4. 訓(xùn)練過程與關(guān)鍵參數(shù)調(diào)優(yōu)——從loss曲線到最終收斂4.1 超參數(shù)如何初始化訓(xùn)練開始前需要先定幾個(gè)關(guān)鍵超參數(shù)batch size、學(xué)習(xí)率、優(yōu)化器、訓(xùn)練輪數(shù)。我的默認(rèn)配置是這樣的。Batch size設(shè)為32。如果顯存不夠就降到16梯度累積到32的等效效果也行。批次太小會(huì)讓梯度估計(jì)噪聲大訓(xùn)練曲線抖得厲害批次太大會(huì)讓模型陷入尖銳最小值泛化能力反而下降32是起步很穩(wěn)的值。優(yōu)化器優(yōu)先用Adam初始學(xué)習(xí)率1e-4。Adam的自適應(yīng)學(xué)習(xí)率機(jī)制讓它在不熟悉的任務(wù)上比SGD穩(wěn)定得多新手不容易翻車。如果你用的是SGD加動(dòng)量學(xué)習(xí)率初始值要設(shè)到1e-2或1e-3同時(shí)配合CosineAnnealing學(xué)習(xí)率調(diào)度這個(gè)組合調(diào)好了效果更好但需要更多調(diào)試經(jīng)驗(yàn)初學(xué)者我不推薦一上來就碰。訓(xùn)練輪數(shù)先設(shè)50輪但配合“早停”機(jī)制。我的做法是每一輪訓(xùn)練結(jié)束后用驗(yàn)證集做一次評估如果驗(yàn)證集準(zhǔn)確率連續(xù)8輪沒有提升就停止訓(xùn)練并保存歷史最優(yōu)模型。注意保存模型時(shí)一定要保存“驗(yàn)證集準(zhǔn)確率最高”的那個(gè)epoch的權(quán)重而不是最后一個(gè)epoch。最后一輪的模型往往已經(jīng)過擬合了我用這個(gè)方式至少提升了3到5個(gè)百分點(diǎn)的測試準(zhǔn)確率。4.2 學(xué)習(xí)率與優(yōu)化器選擇背后的邏輯為什么要單獨(dú)解釋這個(gè)因?yàn)閷W(xué)習(xí)率的設(shè)置直接影響整個(gè)訓(xùn)練的成敗。如果學(xué)習(xí)率太大loss會(huì)在某個(gè)值附近劇烈震蕩就是不下降如果學(xué)習(xí)率太小模型學(xué)得比蝸牛還慢訓(xùn)練50輪可能還在原地打轉(zhuǎn)。你可以把學(xué)習(xí)率理解成“下山時(shí)每一步邁多大”。預(yù)訓(xùn)練模型已經(jīng)站在一個(gè)離谷底很近的地方你只需要小步試探所以學(xué)習(xí)率要比從零訓(xùn)練小很多。用1e-4到1e-5這個(gè)區(qū)間通常比較安全。如果你發(fā)現(xiàn)訓(xùn)練一開始loss就飆高先檢查是不是學(xué)習(xí)率設(shè)置過大、數(shù)據(jù)歸一化漏掉了或者標(biāo)簽錯(cuò)位了反而不用急著調(diào)網(wǎng)絡(luò)結(jié)構(gòu)。關(guān)于Adam和SGD的選擇另一個(gè)實(shí)用技巧是先用Adam跑出當(dāng)前數(shù)據(jù)的“性能上限”最后再用SGD加余弦退火做最后沖刺有時(shí)候能再提升一點(diǎn)準(zhǔn)確率。這個(gè)方法不是必須的但如果你時(shí)間充裕值得一試。4.3 過擬合的識(shí)別與應(yīng)對課程設(shè)計(jì)項(xiàng)目里過擬合是最常見的現(xiàn)象尤其在數(shù)據(jù)集規(guī)模小的情況下。它的典型特征是訓(xùn)練集loss持續(xù)下降準(zhǔn)確率一路走高但驗(yàn)證集loss先降后升準(zhǔn)確率上升到某個(gè)點(diǎn)就開始下降或停滯。怎么識(shí)別最簡單的方法是每輪訓(xùn)練后記錄train_loss和val_loss并繪制兩條曲線。如果兩條曲線從一開始就分道揚(yáng)鑣說明模型容量過大或數(shù)據(jù)量太少需要加正則化如果前期同步下降、后期val_loss開始反彈說明訓(xùn)練后期在死記硬背訓(xùn)練集需要早?;蚋鼜?qiáng)的數(shù)據(jù)增強(qiáng)。應(yīng)對手段我按優(yōu)先級排序第一加強(qiáng)數(shù)據(jù)增強(qiáng)這是治本的方式讓模型見識(shí)更多變化第二增加Dropout比例全連接層從0.3提到0.5是立竿見影的第三降低學(xué)習(xí)率讓模型學(xué)慢一點(diǎn)減少對訓(xùn)練集細(xì)節(jié)的記住第四把ResNet18換成MobileNetV3減小模型容量以匹配數(shù)據(jù)規(guī)模。前兩個(gè)手段通常就能解決問題最后的手段需要謹(jǐn)慎使用別為了防過擬合犧牲太多擬合能力。4.4 訓(xùn)練過程中容易出現(xiàn)的問題我在這個(gè)項(xiàng)目里遇到過幾個(gè)比較典型的訓(xùn)練異常這里列出來供你對照參考。Loss變成NaN本質(zhì)上是因?yàn)橛?jì)算過程中出現(xiàn)了數(shù)值溢出常見原因是學(xué)習(xí)率過大、數(shù)據(jù)里有異常值比如像素值未歸一化、或者損失函數(shù)計(jì)算方式有問題。先檢查歸一化再把學(xué)習(xí)率降到1e-5一般能解決。訓(xùn)練loss不下降首先排除是不是數(shù)據(jù)沒喂對比如標(biāo)簽錯(cuò)位、增強(qiáng)后圖片通道數(shù)不對。我排查的順序是打印一個(gè)batch的樣本人工檢查圖片和標(biāo)簽是否對應(yīng)再檢查模型的輸出維度是否等于類別數(shù)最后再考慮學(xué)習(xí)率問題。驗(yàn)證集準(zhǔn)確率震蕩劇烈如果epoch間的準(zhǔn)確率波動(dòng)超過5%往往是batch size太小、或者訓(xùn)練和驗(yàn)證數(shù)據(jù)分布不一致。檢查數(shù)據(jù)集劃分有沒有缺口或者有無某個(gè)類別的圖片全部混進(jìn)了驗(yàn)證集。4.5 一個(gè)完整的訓(xùn)練配置參考我把一套自己實(shí)測穩(wěn)定的配置寫成代碼片段你可以直接拿去改。這里用PyTorch框架因?yàn)檎n程設(shè)計(jì)的常見基礎(chǔ)就是它。import torch import torch.nn as nn import torch.optim as optim from torchvision import models, transforms # 數(shù)據(jù)預(yù)處理 train_transform transforms.Compose([ transforms.Resize(256), transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.05), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 模型加載預(yù)訓(xùn)練ResNet50微調(diào) model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) num_features model.fc.in_features model.fc nn.Linear(num_features, 10) # 10個(gè)類別 # 訓(xùn)練參數(shù) optimizer optim.Adam(model.parameters(), lr1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size20, gamma0.1) criterion nn.CrossEntropyLoss() best_val_acc 0.0 patience 8 wait 0 for epoch in range(50): model.train() running_loss 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() model.eval() val_acc 0.0 total 0 with torch.no_grad(): for images, labels in val_loader: outputs model(images) _, predicted torch.max(outputs, 1) val_acc (predicted labels).sum().item() total labels.size(0) val_acc val_acc / total scheduler.step() if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pth) wait 0 else: wait 1 if wait patience: print(fEarly stopping at epoch {epoch}) break print(fEpoch {epoch}: loss{running_loss:.4f}, val_acc{val_acc:.4f})注意幾個(gè)小點(diǎn)CrossEntropyLoss內(nèi)部已經(jīng)包含了softmax所以模型最后一層直接輸出原始logits就行不要自己額外接softmax再做損失計(jì)算scheduler的step要在每次epoch結(jié)束后調(diào)用模型保存用state_dict而不是整個(gè)對象加載時(shí)要先load這個(gè)state_dict再model.eval()。5. 評估別只看準(zhǔn)確率——混淆矩陣和錯(cuò)誤分析5.1 為什么準(zhǔn)確率會(huì)騙人很多同學(xué)訓(xùn)練完第一反應(yīng)就是打印一行的測試集準(zhǔn)確率然后看個(gè)數(shù)字就收工。但在這個(gè)題目上準(zhǔn)確率真的會(huì)騙人。假如你有10個(gè)類別其中“蛤蜊”圖片占全部數(shù)據(jù)的30%而“鮑魚”只占3%一個(gè)把所有圖都判斷為“蛤蜊”的模型準(zhǔn)確率也能有30%。如果分布在訓(xùn)練和測試階段比例懸殊這個(gè)數(shù)字更沒有參考意義。所以第一步要做的是計(jì)算每個(gè)類別的精準(zhǔn)率、召回率和F1分?jǐn)?shù)。精準(zhǔn)率是“模型認(rèn)為是某類的結(jié)果中有多少是真的這類”召回率是“這類樣本中有多少比例被正確識(shí)別出來”。這兩個(gè)指標(biāo)會(huì)暴露模型的真實(shí)偏好有些類別模型的召回率高但精準(zhǔn)率低說明它傾向于“什么都往里裝”有些類別精準(zhǔn)率高但召回率低說明它謹(jǐn)慎但容易漏檢。5.2 混淆矩陣怎么用混淆矩陣是評估分類模型最直觀的工具它把每個(gè)類別的真實(shí)標(biāo)簽和預(yù)測結(jié)果做成一個(gè)二維方陣。對角線上的數(shù)字越大越好對角線之外的格子則顯示了具體哪些類別之間容易混淆。在殼類識(shí)別里混淆矩陣的價(jià)值尤其大。貝殼類之間的外觀相似性很高尤其是顏色接近、紋理相近的類別。比如花甲和蛤蜊形狀都是橢圓形表面都有類似的放射紋牡蠣和扇貝都有比較明顯的不規(guī)則外殼海螺和螺螄雖然大小差異很大但在某些拍攝角度下輪廓線條非常接近。當(dāng)我第一次畫混淆矩陣時(shí)發(fā)現(xiàn)“花甲”被錯(cuò)判成“蛤蜊”的比例高達(dá)20%這說明模型在兩種類型的紋理細(xì)節(jié)上區(qū)分還不夠。這時(shí)候就有針對性的方案增加這兩種類別在訓(xùn)練集中的比例、對這兩種類別的圖片做更細(xì)的CRP數(shù)據(jù)增強(qiáng)、或者從網(wǎng)絡(luò)底層為它們增加一個(gè)二分類器做輔助判斷。沒有混淆矩陣的指引這些優(yōu)化是無從下手的。用PyTorch畫混淆矩陣并不復(fù)雜核心步驟是在測試集上跑一遍模型把每個(gè)樣本的真實(shí)標(biāo)簽和預(yù)測標(biāo)簽分別存進(jìn)兩個(gè)列表再用sklearn的confusion_matrix函數(shù)生成矩陣最后用matplotlib的imshow或seaborn的heatmap畫出來。你要是不知道從哪里入手我可以給你一個(gè)簡化版的代碼骨架from sklearn.metrics import confusion_matrix import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 假設(shè)你已得到真實(shí)標(biāo)簽列表 y_true 和預(yù)測標(biāo)簽列表 y_pred cm confusion_matrix(y_true, y_pred) plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(Predicted) plt.ylabel(True) plt.show()5.3 錯(cuò)誤案例分析的價(jià)值比混淆矩陣更進(jìn)一步的是對錯(cuò)誤樣本做逐例分析。我建議把預(yù)測錯(cuò)誤的圖片單獨(dú)拎出來按照“真實(shí)類別”和“預(yù)測類別”分組保存然后人工看一遍你會(huì)發(fā)現(xiàn)幾個(gè)高頻錯(cuò)誤模式。一種常見的錯(cuò)誤模式是背景干擾。比如“扇貝”的圖片背景里恰好有一塊紋理像“牡蠣”外殼的石頭模型就被帶偏了。這類錯(cuò)誤說明模型確實(shí)學(xué)到了背景特征數(shù)據(jù)增強(qiáng)或者更換訓(xùn)練集可以改善。另一種錯(cuò)誤模式是拍攝角度導(dǎo)致的形態(tài)失真。有些貝殼在不同角度下呈現(xiàn)完全不同的形狀比如從正上方看是圓形從側(cè)面看是長條形。如果訓(xùn)練集會(huì)此類角度分布不均勻模型就會(huì)對某個(gè)角度產(chǎn)生偏好實(shí)際測試時(shí)一旦遇到新角度就容易出錯(cuò)。此時(shí)可以考慮增加多角度拍攝的樣本或者利用自定義網(wǎng)絡(luò)在輸入階段加一個(gè)“多尺度特征”的處理。把這些錯(cuò)誤案例整理成PowerPoint答辯時(shí)放出來再配上一句“我針對這一類錯(cuò)誤做了哪些優(yōu)化”說服力絕對比單純報(bào)一個(gè)準(zhǔn)確率數(shù)字強(qiáng)得多。這也是課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)在評審時(shí)特別看重的東西他不是只看你跑通了代碼而是看你有沒有分析問題、迭代優(yōu)化。5.4 消融實(shí)驗(yàn)與對比基線這個(gè)環(huán)節(jié)屬于“加分項(xiàng)”。如果你時(shí)間充足可以設(shè)計(jì)一組簡單的對比實(shí)驗(yàn)證明你的選擇是合理的。我的建議是三組對比第一組用從零訓(xùn)練的ResNet18第二組用微調(diào)的ResNet18第三組用微調(diào)的ResNet50。三組使用相同的數(shù)據(jù)劃分、相同的訓(xùn)練參數(shù)只看準(zhǔn)確率和F1分?jǐn)?shù)的差異。這三組對比能回答兩個(gè)問題遷移學(xué)習(xí)到底帶來了多少提升更深的模型在這個(gè)數(shù)據(jù)集上有沒有優(yōu)勢我實(shí)測的結(jié)果是從零訓(xùn)練ResNet18的準(zhǔn)確率約76%微調(diào)ResNet18約88%微調(diào)ResNet50約91%。這三個(gè)數(shù)據(jù)放在報(bào)告里根本不需要你自己吹評審老師自己就能看出設(shè)計(jì)脈絡(luò)。6. 常見問題與排查技巧實(shí)錄——給后來者的一份避坑清單6.1 訓(xùn)練集很小準(zhǔn)確率提不上去怎么辦有不少同學(xué)反饋說自己只收集到兩三百張圖片網(wǎng)絡(luò)再怎么調(diào)也突破不了85%。這種情況的共同問題是數(shù)據(jù)集太小靠增網(wǎng)結(jié)構(gòu)已經(jīng)不能解決。我的建議是優(yōu)先放棄高參數(shù)預(yù)算的模型轉(zhuǎn)而用MobileNetV3Small這樣的輕量級網(wǎng)絡(luò)它參數(shù)量少過擬合風(fēng)險(xiǎn)更低在這個(gè)數(shù)據(jù)規(guī)模下反而可能表現(xiàn)更好。同時(shí)數(shù)據(jù)增強(qiáng)的幅值可以稍微放寬松一點(diǎn)比如ColorJitter的亮度從0.2提到0.3Rotation從15度提到20度給模型創(chuàng)造更多“偽樣本”。如果條件允許還有一個(gè)更實(shí)用的方案用數(shù)據(jù)增強(qiáng)庫做在線擴(kuò)充每輪訓(xùn)練的圖片都不同從而間接讓模型看到更豐富的變化。這不是真正增加數(shù)據(jù)集但對提升泛化能力有幫助。6.2 模型在訓(xùn)練集上表現(xiàn)好驗(yàn)證集上卻很差這種癥狀幾乎就是過擬合的典型畫像。回想一下你的設(shè)置epochs是否設(shè)得太大有沒有早停機(jī)制Dropout比例是不是為0數(shù)據(jù)增強(qiáng)有沒有加上去如果三問全中那問題就很明白了。我的排查過程一般按這個(gè)順序來命中率很高第一步加上早停第二步加Dropout(0.5)第三步增強(qiáng)數(shù)據(jù)增強(qiáng)策略做完這三件事絕大多數(shù)過擬合都能緩解。6.3 驗(yàn)證集準(zhǔn)確率正常到測試集上突然掉了這個(gè)現(xiàn)象通常指向數(shù)據(jù)分布不一致。比如驗(yàn)證集和訓(xùn)練集都來自同一個(gè)來源背景和拍攝條件高度相似但測試集是另一個(gè)來源的網(wǎng)絡(luò)圖片背景不同測試準(zhǔn)確率自然掉。解決辦法是在劃分?jǐn)?shù)據(jù)集時(shí)盡量打亂所有來源的圖片再按類別分層隨機(jī)劃分保證三個(gè)集合的圖片來源分布一致。還有一個(gè)容易忽略的點(diǎn)測試圖片輸入模型前一定要做和驗(yàn)證集完全一樣的預(yù)處理流程少了歸一化或縮放不一致效果都會(huì)明顯打折。6.4 顯存不夠用怎么辦顯卡顯存小于4GB的時(shí)候加載ResNet50和一批32張224x224的圖片確實(shí)可能OOM。我的建議是依次嘗試三招一、把batch size降到8或16二、把輸入圖片從224x224降到192x192這在大多數(shù)場景下對準(zhǔn)確率影響很小三、如果還不行就換MobileNetV3或ShuffleNetV2它們的顯存占用遠(yuǎn)低于ResNet。還有一招比較冷門使用梯度累積。即batch_size8的情況下每4個(gè)batch做一次優(yōu)化器更新效果等同于batch_size32。這個(gè)辦法能讓你在小顯存上跑出大batch的效果代碼實(shí)現(xiàn)也不復(fù)雜把optimizer.zero_grad()移到每4步執(zhí)行一次就行。6.5 訓(xùn)練時(shí)間太長怎么辦如果你只有一塊中低端顯卡訓(xùn)練50輪ResNet50可能要兩三個(gè)小時(shí)這個(gè)時(shí)間對課程設(shè)計(jì)來說還能接受但如果你想多跑幾組對比實(shí)驗(yàn)就會(huì)覺得很煎熬。我的建議是先用50張圖片做一輪“冒煙測試”確認(rèn)代碼能跑通、loss能下降再全量訓(xùn)練。這個(gè)測試能幫你省下大量調(diào)試時(shí)間。正式訓(xùn)練時(shí)如果只是想看趨勢可以只跑30輪如果要最終結(jié)果再跑滿早停機(jī)制允許的最多輪數(shù)。6.6 一點(diǎn)額外的實(shí)操心得最后分享幾個(gè)細(xì)節(jié)經(jīng)驗(yàn)雖然看起來不起眼但實(shí)際項(xiàng)目中影響很大。第一在每個(gè)epoch結(jié)束后除了保存模型我還要把訓(xùn)練集和驗(yàn)證集的loss、準(zhǔn)確率寫進(jìn)一個(gè)CSV文件或者記錄在日志里。訓(xùn)練結(jié)束之后用這個(gè)文件畫曲線比從終端里翻打印信息靠譜一百倍。第二模型訓(xùn)練結(jié)束后可以用Grad-CAM熱力圖可視化幾張測試圖片的決策依據(jù)看看模型究竟在關(guān)注貝殼的哪個(gè)部位。這對答辯時(shí)解釋模型行為很有幫助比如展示“模型之所以能把扇貝和海螺區(qū)分開是因?yàn)樗陉P(guān)注殼頂?shù)姆派渚€”。Grad-CAM的實(shí)現(xiàn)并不復(fù)雜網(wǎng)上也有很多現(xiàn)成代碼。第三課程設(shè)計(jì)答辯時(shí)評審老師絕大多數(shù)不會(huì)深究你模型實(shí)現(xiàn)的所有細(xì)節(jié)但他們一定會(huì)問“你做的這個(gè)東西到底有什么實(shí)際應(yīng)用”建議你提前準(zhǔn)備一兩個(gè)應(yīng)用場景比如“可以用于海鮮市場的自動(dòng)結(jié)算臺(tái)通過攝像頭識(shí)別殼體類別幫助結(jié)算”或“可以用于海洋生態(tài)調(diào)查中的殼體樣本自動(dòng)歸類”。有一個(gè)清晰的應(yīng)用落地陳述整個(gè)項(xiàng)目的完成度會(huì)瞬間被拉高。做這個(gè)項(xiàng)目的整個(gè)過程我最深的體會(huì)是千萬不要把“跑通代碼”當(dāng)成目標(biāo)。跑通只是一個(gè)起點(diǎn)真正讓你有收獲的是后面那段“數(shù)據(jù)清洗、調(diào)參、分析錯(cuò)誤、再調(diào)”的循環(huán)。我第一次做這個(gè)題目的時(shí)候最痛苦的不是代碼寫不出來而是面對一個(gè)89%的準(zhǔn)確率不知道下一步該干什么。后來學(xué)會(huì)了看混淆矩陣、看錯(cuò)誤樣本、查損失曲線才真正有了“調(diào)模型是科學(xué)而不是玄學(xué)”的感覺。如果你在做的過程中也卡在某個(gè)準(zhǔn)確率上不去不妨停下來先去把你的驗(yàn)證集錯(cuò)誤圖片一張張看過去答案往往就藏在那些照片里。