習(xí)水果識(shí)別項(xiàng)目實(shí)戰(zhàn):從數(shù)據(jù)預(yù)處理到模型部署全解析)
很多做課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)的朋友一上來(lái)就盯著“深度學(xué)習(xí)”和“識(shí)別”這些關(guān)鍵詞覺得水果識(shí)別項(xiàng)目無(wú)非是套個(gè)模型調(diào)參跑準(zhǔn)確率。但我做了幾年圖像相關(guān)項(xiàng)目之后回頭看這個(gè)題目能火不是沒(méi)有道理——它的工程量非常完整從數(shù)據(jù)到訓(xùn)練到評(píng)估再到部署幾乎把機(jī)器學(xué)習(xí)流程里所有“能踩的坑”都給你安排了一遍。這篇文章把完整思路和實(shí)操記錄做個(gè)復(fù)盤給正在做或者準(zhǔn)備做這個(gè)題目的同學(xué)一份可以直接抄作業(yè)的參考。1. 項(xiàng)目現(xiàn)狀與整體設(shè)計(jì)思路1.1 這類題目到底在練什么水果分類任務(wù)本身并不難難的是把一個(gè)完整的深度學(xué)習(xí)項(xiàng)目“走通”。很多同學(xué)以為難點(diǎn)在模型結(jié)構(gòu)實(shí)際上真正拉開差距的地方在于你拿到的原始圖片質(zhì)量參差不齊光線、背景、遮擋、拍攝角度全都不一樣數(shù)據(jù)預(yù)處理和增強(qiáng)策略如果沒(méi)有想清楚再好的模型也白搭。這個(gè)項(xiàng)目最大的價(jià)值就是逼迫你把圖像分類的完整鏈路自己動(dòng)手過(guò)一遍。從任務(wù)類型上看這屬于典型的單標(biāo)簽圖像分類問(wèn)題輸入是一張RGB圖片輸出是預(yù)定義水果類別上的概率分布。常見的類別數(shù)量一般控制在5到15類之間比如蘋果、香蕉、橘子、葡萄、西瓜、草莓、菠蘿這類生活中最常見的品種。類別太少體現(xiàn)不出模型能力類別太多數(shù)據(jù)標(biāo)注成本會(huì)迅速上升5到15類正好是課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)的“舒適區(qū)”。我遇到的很多參考項(xiàng)目中大家不約而同地選擇了公開的水果數(shù)據(jù)集而不是自己拍攝。原因很現(xiàn)實(shí)自己拍攝需要解決光照一致性、背景統(tǒng)一、數(shù)量充足這些個(gè)麻煩問(wèn)題幾小時(shí)拍下來(lái)可能只剩幾十張能用的圖根本不夠訓(xùn)練。公開數(shù)據(jù)集動(dòng)輒幾百個(gè)類別、上萬(wàn)張圖片篩選自己需要的類別非常方便。數(shù)據(jù)量上來(lái)了模型才能學(xué)到真正穩(wěn)定的特征。1.2 技術(shù)選型方案對(duì)比框架選擇上PyTorch是當(dāng)前高校課程設(shè)計(jì)和科研項(xiàng)目里最常見的選擇它的動(dòng)態(tài)圖機(jī)制讓調(diào)試非常直觀打印中間變量、打斷點(diǎn)、臨時(shí)代碼修改都很順手。TensorFlow也有不少人在用但2.x版本之后的接口變動(dòng)比較大很多網(wǎng)上找的教程代碼直接跑不通。如果是以快速完成項(xiàng)目為目標(biāo)我建議直接選PyTorch穩(wěn)定版配套資源多遇到報(bào)錯(cuò)也好搜索。模型的選型在我看過(guò)的方案里基本分為兩派方案類型典型結(jié)構(gòu)優(yōu)點(diǎn)缺點(diǎn)自定義輕量網(wǎng)絡(luò)3到5層卷積全連接結(jié)構(gòu)可控、參數(shù)少、訓(xùn)練快準(zhǔn)確率上限不高容易欠擬合預(yù)訓(xùn)練模型遷移ResNet、MobileNet、EfficientNet收斂快、準(zhǔn)確率高、省顯存依賴預(yù)訓(xùn)練權(quán)重模型體積較大說(shuō)實(shí)話對(duì)于水果識(shí)別這個(gè)任務(wù)自定義小網(wǎng)絡(luò)在公開數(shù)據(jù)集上往往能跑到90%左右的準(zhǔn)確率足夠應(yīng)付課程設(shè)計(jì)了。如果目標(biāo)是畢業(yè)設(shè)計(jì)想拿到95%以上的穩(wěn)定準(zhǔn)確率加魯棒性那直接用遷移學(xué)習(xí)更劃算。遷移學(xué)習(xí)的原理也很好理解模型之前在ImageNet那1000類上學(xué)到過(guò)大量通用的紋理、邊緣、形狀特征這些特征對(duì)識(shí)別水果同樣有效。我們只是把最后一層全連接替換成自己的類別數(shù)然后讓模型在水果數(shù)據(jù)上微調(diào)一下權(quán)重。這比從頭訓(xùn)練要快得多也省數(shù)據(jù)。2. 數(shù)據(jù)集準(zhǔn)備基礎(chǔ)不牢地動(dòng)山搖2.1 水果數(shù)據(jù)從哪里來(lái)關(guān)于公開數(shù)據(jù)集的選取我在實(shí)操中摸索出幾個(gè)參考維度。首先是圖片數(shù)量每個(gè)類別最好在500張以上少了容易過(guò)擬合。其次是背景多樣性盡量選那些圖片背景不單一的數(shù)據(jù)集這樣訓(xùn)練出來(lái)的模型在新環(huán)境下泛化能力更好。最后是分辨率統(tǒng)一性原始數(shù)據(jù)集圖片尺寸參差不齊沒(méi)關(guān)系后面可以統(tǒng)一處理但如果差異過(guò)大縮放會(huì)嚴(yán)重變形這一步一定要提前留個(gè)心眼。我之前看到一個(gè)學(xué)生項(xiàng)目里用過(guò)一份純白背景的水果數(shù)據(jù)集訓(xùn)練集和驗(yàn)證集準(zhǔn)確率都到了98%結(jié)果拿自己的手機(jī)拍一張照片去測(cè)準(zhǔn)確率直接跌到70%以下。原因就是用純白背景訓(xùn)練模型把“白背景”當(dāng)成了識(shí)別水果的重要特征。這個(gè)教訓(xùn)相當(dāng)?shù)湫秃竺鎸?duì)比數(shù)據(jù)集篩選的時(shí)候一定要考慮訓(xùn)練背景和測(cè)試背景的差異。處理原始數(shù)據(jù)已經(jīng)是比較磨人的工作了。我用一個(gè)簡(jiǎn)單的腳本把原始圖片統(tǒng)一改名為類別名加編號(hào)方便后續(xù)管理import os import shutil from pathlib import Path source_dir Path(raw_images) target_dir Path(dataset) for idx, img_path in enumerate(source_dir.glob(*.jpg)): label apple # 實(shí)際使用時(shí)根據(jù)目錄名或元數(shù)據(jù)確定 new_name f{label}_{idx:05d}.jpg shutil.copy(img_path, target_dir / new_name)2.2 預(yù)處理與數(shù)據(jù)增強(qiáng)策略數(shù)據(jù)預(yù)處理的幾個(gè)標(biāo)準(zhǔn)步驟統(tǒng)一分辨率、歸一化、數(shù)據(jù)增強(qiáng)。分辨率我一般統(tǒng)一到224x224這是ImageNet預(yù)訓(xùn)練模型的標(biāo)準(zhǔn)輸入尺寸同時(shí)也是速度與信息保留的一個(gè)均衡點(diǎn)。如果用小模型訓(xùn)練也可以降到128x128訓(xùn)練速度明顯提升但要注意小尺寸可能丟失紋理細(xì)節(jié)比如蘋果表皮和梨表皮的區(qū)別就會(huì)變得模糊。歸一化這一步很多人容易忽略。PyTorch的預(yù)訓(xùn)練模型默認(rèn)期望輸入是經(jīng)過(guò)特定均值和標(biāo)準(zhǔn)差縮放的這組數(shù)值跟ImageNet統(tǒng)計(jì)數(shù)據(jù)的均值標(biāo)準(zhǔn)差保持一致就行mean [0.485, 0.456, 0.406] std [0.229, 0.224, 0.225]數(shù)據(jù)增強(qiáng)我用了隨機(jī)水平翻轉(zhuǎn)、隨機(jī)旋轉(zhuǎn)、隨機(jī)裁剪和顏色抖動(dòng)這四個(gè)組合。增強(qiáng)不是越多越好有些增強(qiáng)操作反而會(huì)破壞關(guān)鍵特征。比如對(duì)葡萄這種以顏色和形狀為主要特征的水果過(guò)強(qiáng)的色彩抖動(dòng)會(huì)讓模型學(xué)到錯(cuò)誤的信息。我當(dāng)時(shí)在顏色抖動(dòng)上只調(diào)了很小的幅度亮度、飽和度、對(duì)比度變化范圍都控制在10到15%。用torchvision的transforms組合增強(qiáng)非常方便from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomRotation(15), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.1, contrast0.1, saturation0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])2.3 數(shù)據(jù)劃分與加載器的編寫數(shù)據(jù)集的劃分需要遵守一個(gè)基本原則測(cè)試集絕對(duì)不能參與訓(xùn)練過(guò)程連看都不能看否則就是數(shù)據(jù)泄漏得到的準(zhǔn)確率毫無(wú)參考價(jià)值。我通常按照721比例劃分訓(xùn)練驗(yàn)證測(cè)試三個(gè)集合而且要用分層抽樣保證每個(gè)類別在各集合里的比例與整體一致。目錄結(jié)構(gòu)采用最常見的形式dataset/ train/ apple/ banana/ orange/ val/ apple/ banana/ orange/ test/ apple/ banana/ orange/加載器我用的是torchvision的ImageFolder配合DataLoader。設(shè)置批次大小的時(shí)候要考慮顯存大小我常用的顯存配置情況下batch size設(shè)成32比較穩(wěn)妥batch太大雖然梯度更穩(wěn)但顯存容易爆掉尤其是用了ResNet這種大模型之后。from torchvision import datasets train_dataset datasets.ImageFolder(dataset/train, transformtrain_transform) val_dataset datasets.ImageFolder(dataset/val, transformval_transform) train_loader torch.utils.data.DataLoader( train_dataset, batch_size32, shuffleTrue, num_workers4 ) val_loader torch.utils.data.DataLoader( val_dataset, batch_size32, shuffleFalse, num_workers4 )3. 網(wǎng)絡(luò)模型與核心訓(xùn)練代碼實(shí)現(xiàn)3.1 遷移學(xué)習(xí)的具體做法模型這塊我最終選了ResNet18。為什么不是ResNet50或者更大的EfficientNet第一水果識(shí)別的特征相對(duì)簡(jiǎn)單用50層的網(wǎng)絡(luò)純屬殺雞用牛刀第二課程設(shè)計(jì)的環(huán)境通常不具備很好的GPU條件大模型訓(xùn)練周期長(zhǎng)迭代調(diào)參的效率會(huì)非常低。ResNet18的34.4%參數(shù)比ResNet50小很多在精度和資源消耗之間取得了很好的平衡。用PyTorch加載預(yù)訓(xùn)練模型并替換分類頭代碼非常簡(jiǎn)潔import torch import torch.nn as nn from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_features model.fc.in_features num_classes len(train_dataset.classes) model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(num_features, 256), nn.ReLU(inplaceTrue), nn.Linear(256, num_classes) ) model model.to(device)替換分類頭的時(shí)候有幾個(gè)細(xì)節(jié)需要格外注意。第一個(gè)是Dropout的加入預(yù)訓(xùn)練模型的特征提取層已經(jīng)具備很強(qiáng)的表征能力但新加的隨機(jī)初始化全連接層很容易過(guò)擬合Dropout能有效緩解這個(gè)問(wèn)題。第二個(gè)是中間層的維度256這個(gè)值我是參考了幾篇實(shí)踐文章后定的太小會(huì)損失信息太大又增加過(guò)擬合風(fēng)險(xiǎn)。第三是凍結(jié)與解凍的策略問(wèn)題。關(guān)于凍結(jié)與解凍我在項(xiàng)目初期把所有卷積層參數(shù)都凍結(jié)掉只訓(xùn)練新加的全連接層。這樣做的優(yōu)點(diǎn)非常明顯訓(xùn)練速度快、參數(shù)量極少、不容易過(guò)擬合。但我也發(fā)現(xiàn)一個(gè)局限——如果數(shù)據(jù)集和目標(biāo)域差異較大凍結(jié)所有層的效果就不夠理想了。所以我后來(lái)選擇了折中方案凍結(jié)前幾層解凍后幾層讓高層特征能根據(jù)水果數(shù)據(jù)做一定程度的調(diào)整。# 凍結(jié)所有層 for param in model.parameters(): param.requires_grad False # 解凍最后兩個(gè)殘差塊 for param in model.layer4.parameters(): param.requires_grad True3.2 損失函數(shù)與優(yōu)化器選型思路損失函數(shù)方面多分類問(wèn)題基本上就是交叉熵?fù)p失這一點(diǎn)沒(méi)有太多糾結(jié)空間。但優(yōu)化器的選擇我做了幾組對(duì)比實(shí)驗(yàn)。Adam收斂快曲線平穩(wěn)適合快速看到效果SGD帶動(dòng)量收斂稍慢但最終精度往往更高而且泛化性能好一些。我做課程設(shè)計(jì)的時(shí)候用Adam因?yàn)闀r(shí)間緊任務(wù)重快速出結(jié)果最重要。如果是做畢業(yè)設(shè)計(jì)想要沖高精度我建議先用Adam跑五十個(gè)輪次然后把優(yōu)化器換成帶動(dòng)量的SGD用小學(xué)習(xí)率繼續(xù)微調(diào)往往還能再漲兩個(gè)點(diǎn)。學(xué)習(xí)率策略這里再啰嗦一句。直接用固定學(xué)習(xí)率會(huì)導(dǎo)致后期在最優(yōu)解附近來(lái)回震蕩我用了CosineAnnealingLR余弦退火策略讓學(xué)習(xí)率隨著訓(xùn)練輪次逐步衰減效果比固定學(xué)習(xí)率穩(wěn)定很多。optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) criterion nn.CrossEntropyLoss()3.3 完整訓(xùn)練循環(huán)的編寫邏輯訓(xùn)練循環(huán)本身不需要什么奇技淫巧但有一個(gè)好習(xí)慣需要養(yǎng)成每個(gè)輪次都要同時(shí)記錄訓(xùn)練集和驗(yàn)證集的損失與準(zhǔn)確率保存最優(yōu)模型。只盯訓(xùn)練集準(zhǔn)確率是初學(xué)者最容易犯的錯(cuò)訓(xùn)練集上表現(xiàn)好不能說(shuō)明任何問(wèn)題。for epoch in range(epochs): model.train() train_loss, train_correct 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() train_loss loss.item() * images.size(0) train_correct (outputs.argmax(1) labels).sum().item() model.eval() val_loss, val_correct 0.0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) val_loss criterion(outputs, labels).item() * images.size(0) val_correct (outputs.argmax(1) labels).sum().item() train_acc train_correct / len(train_dataset) val_acc val_correct / len(val_dataset) torch.save(model.state_dict(), last_model.pth) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth) scheduler.step() print(fEpoch {epoch1}/{epochs} | fTrain Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f})輪次設(shè)置上我用30輪作為初始值配合早停機(jī)制——連續(xù)8輪驗(yàn)證集準(zhǔn)確率不上升就停止訓(xùn)練這樣可以有效防止后期無(wú)效訓(xùn)練浪費(fèi)時(shí)間。4. 模型評(píng)估與結(jié)果分析優(yōu)化4.1 評(píng)價(jià)指標(biāo)的正確打開方式準(zhǔn)確率是最直觀的評(píng)價(jià)指標(biāo)但在樣本不均衡或者某幾類易混淆的情況下單獨(dú)看準(zhǔn)確率會(huì)掩蓋問(wèn)題。類別不平衡場(chǎng)景下需要額外關(guān)注的是各類別的召回率和精確率。舉個(gè)具體例子如果訓(xùn)練集中蘋果圖片有2000張菠蘿只有300張模型可能直接放棄菠蘿這類整體準(zhǔn)確率看起來(lái)依然很高但菠蘿的召回率會(huì)非常難看。我在項(xiàng)目中除了打印整體準(zhǔn)確率還會(huì)輸出每一類的精確率、召回率和F1值。水果識(shí)別中常見的混淆對(duì)——蘋果和梨、橘子和橙子光看總體指標(biāo)很難發(fā)現(xiàn)它們之間的混淆問(wèn)題逐類別分析一清二楚from sklearn.metrics import classification_report # 假設(shè)y_true為真實(shí)標(biāo)簽列表y_pred為預(yù)測(cè)標(biāo)簽列表 print(classification_report( y_true, y_pred, target_namestrain_dataset.classes, digits4 ))除了指標(biāo)本身可視化還包括損失曲線和準(zhǔn)確率曲線的繪制。普通做法是把訓(xùn)練和驗(yàn)證的曲線畫在同一張圖里觀察二者的差距可以判斷模型的擬合狀態(tài)。訓(xùn)練損失下降而驗(yàn)證損失不降反升就是過(guò)擬合的典型信號(hào)兩者都高則說(shuō)明模型欠擬合需要增加模型容量或者延長(zhǎng)訓(xùn)練時(shí)間。4.2 進(jìn)階優(yōu)化路徑結(jié)果分析完之后優(yōu)化方向大致有幾個(gè)。第一是對(duì)易混淆類別增強(qiáng)樣本蘋果和梨如果分不清楚增加兩種類別在相似角度、相似顏色背景下的樣本量。第二是調(diào)整損失函數(shù)來(lái)懲罰易錯(cuò)樣本比如Label Smoothing給真實(shí)標(biāo)簽的概率做一點(diǎn)平滑處理降低模型對(duì)自身預(yù)測(cè)的過(guò)度自信對(duì)提升泛化能力有幫助。第三是測(cè)試時(shí)的數(shù)據(jù)增強(qiáng)推理時(shí)做多次裁剪然后對(duì)預(yù)測(cè)概率取平均。我實(shí)際測(cè)試過(guò)TTA測(cè)試時(shí)增強(qiáng)把每張測(cè)試圖做水平翻轉(zhuǎn)后兩次預(yù)測(cè)取平均準(zhǔn)確率提升了0.5到1個(gè)百分點(diǎn)。代價(jià)是推理時(shí)間翻倍在課程設(shè)計(jì)演示時(shí)可以加分但在需要實(shí)時(shí)響應(yīng)的場(chǎng)景里不太劃算。最后的模型選擇上很多人只保存了“最佳驗(yàn)證準(zhǔn)確率”的模型。我后來(lái)發(fā)現(xiàn)最佳驗(yàn)證損失對(duì)應(yīng)的模型反而常常表現(xiàn)更好因?yàn)樗鼘?duì)置信度的校準(zhǔn)更加準(zhǔn)確不容易在個(gè)別樣本上過(guò)于激進(jìn)。所以我在項(xiàng)目里同時(shí)保存兩個(gè)指標(biāo)下的最優(yōu)模型對(duì)比之后再選最終版本。5. 模型部署與推理接口封裝5.1 導(dǎo)出與加載推理封裝訓(xùn)練完成后的模型不能光放在PyTorch環(huán)境里自嗨課程設(shè)計(jì)通常需要做一個(gè)演示程序讓人能輸入一張圖片就得到識(shí)別結(jié)果。把這個(gè)過(guò)程封裝成獨(dú)立的推理腳本很有必要。import torch from PIL import Image from torchvision import transforms class FruitClassifier: def __init__(self, model_path, class_names, deviceNone): self.device device or ( torch.device(cuda if torch.cuda.is_available() else cpu) ) self.model models.resnet18() num_features self.model.fc.in_features self.model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(num_features, 256), nn.ReLU(inplaceTrue), nn.Linear(256, len(class_names)) ) self.model.load_state_dict( torch.load(model_path, map_locationself.device) ) self.model.to(self.device) self.model.eval() self.class_names class_names self.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] ) ]) def predict(self, image_path): image Image.open(image_path).convert(RGB) tensor self.transform(image).unsqueeze(0).to(self.device) with torch.no_grad(): outputs self.model(tensor) probs torch.nn.functional.softmax(outputs, dim1) conf, idx torch.max(probs, dim1) return self.class_names[idx.item()], conf.item()如果想把項(xiàng)目包裝得完整一點(diǎn)可以用Flask寫一個(gè)簡(jiǎn)單的HTTP接口讓前端通過(guò)Web方式訪問(wèn)模型。這不難但能讓整個(gè)項(xiàng)目從“訓(xùn)練腳本”上升為“可演示系統(tǒng)”答辯時(shí)印象分完全不一樣。5.2 部署時(shí)容易翻車的細(xì)節(jié)部署時(shí)最容易出的問(wèn)題就是訓(xùn)練和推理的預(yù)處理不一致。訓(xùn)練時(shí)有RandomCrop和RandomRotation推理時(shí)如果也用了隨機(jī)增強(qiáng)結(jié)果會(huì)不穩(wěn)定。正確的推理前處理是先等比縮放到短邊256再中心裁剪到224。還有一個(gè)常見問(wèn)題圖片格式和通道順序。PIL讀取的圖片是RGB但OpenCV讀出來(lái)是BGR很多同學(xué)在部署時(shí)混用了兩種讀取方式導(dǎo)致預(yù)測(cè)完全錯(cuò)亂但代碼檢查不出問(wèn)題。我在推理腳本里統(tǒng)一用PIL讀取并在開頭強(qiáng)制加了一次convert(RGB)把灰度圖或者帶透明通道的PNG統(tǒng)一轉(zhuǎn)成三通道RGB格式。5.3 圖形界面與演示擴(kuò)展方向有同學(xué)問(wèn)我是不是一定要做一個(gè)GUI界面。我的觀點(diǎn)是如果時(shí)間充裕做一個(gè)簡(jiǎn)單的GUI演示能讓項(xiàng)目完整度上一個(gè)臺(tái)階。本地Gradio效果方面都很方便——寫幾行代碼就有一個(gè)Web界面可以上傳圖片、顯示識(shí)別類別和置信度還能拖到手機(jī)上展示。畢業(yè)設(shè)計(jì)我做的就是用Gradio封裝的界面答辯現(xiàn)場(chǎng)直接拿手機(jī)拍了張水果圖片傳上去效果和場(chǎng)面都非常好。import gradio as gr def classify_image(image): label, conf classifier.predict(image) return f識(shí)別結(jié)果: {label} (置信度: {conf:.2%}) gr.Interface( fnclassify_image, inputsgr.Image(typepil), outputsgr.Textbox(), title水果識(shí)別系統(tǒng) ).launch(shareTrue)6. 常見問(wèn)題排查與避坑指南6.1 訓(xùn)練不收斂的排查思路訓(xùn)練Loss居高不下或者直接變成NaN是我被問(wèn)過(guò)最多的問(wèn)題。排查順序很固定——查數(shù)據(jù)、查標(biāo)簽、查學(xué)習(xí)率、查模型輸出。首先確認(rèn)輸入Tensor里沒(méi)有NaN或無(wú)窮值打印一下預(yù)處理后的數(shù)據(jù)比如最大值和最小值圖片歸一化之后值域在-2到2左右出現(xiàn)明顯異常數(shù)值就是數(shù)據(jù)問(wèn)題。其次確認(rèn)label不是從1開始這看起來(lái)無(wú)關(guān)緊要但CrossEntropyLoss要求標(biāo)簽從0開始很多數(shù)據(jù)集索引習(xí)慣從1開始一旦錯(cuò)位整個(gè)訓(xùn)練都會(huì)亂套。學(xué)習(xí)率過(guò)大也是Loss變成NaN的高頻原因尤其是用了預(yù)訓(xùn)練模型之后新加的分類層和預(yù)訓(xùn)練層對(duì)學(xué)習(xí)率的敏感程度不一樣。我見過(guò)一個(gè)Case初始學(xué)習(xí)率直接照抄網(wǎng)上代碼設(shè)成1e-2結(jié)果訓(xùn)練兩次迭代就炸了。用預(yù)訓(xùn)練特征最后一層的學(xué)習(xí)率設(shè)置1e-3起步用全凍結(jié)的純線性分類也才用1e-3。如果模型是隨機(jī)初始化從頭開始訓(xùn)練那倒是可以稍微大一點(diǎn)但必須配合學(xué)習(xí)率預(yù)熱。6.2 過(guò)擬合與欠擬合的判斷和調(diào)整過(guò)擬合的典型表現(xiàn)是訓(xùn)練準(zhǔn)確率持續(xù)逼近100%驗(yàn)證準(zhǔn)確率反而下降。應(yīng)對(duì)措施按優(yōu)先級(jí)排序增加數(shù)據(jù)增強(qiáng)的強(qiáng)度、降低模型復(fù)雜度、加Dropout、減小batch size、加權(quán)重衰減。我通常先調(diào)整數(shù)據(jù)增強(qiáng)強(qiáng)度因?yàn)檫@是零成本的。欠擬合則相反模型結(jié)構(gòu)表達(dá)能力不足或者訓(xùn)練輪次太少。欠擬合的判斷標(biāo)準(zhǔn)是訓(xùn)練集準(zhǔn)確率都上不去這時(shí)需要換更復(fù)雜的模型或者干脆解凍更多預(yù)訓(xùn)練層。6.3 環(huán)境配置和版本兼容問(wèn)題深度學(xué)習(xí)項(xiàng)目里環(huán)境問(wèn)題比算法問(wèn)題更讓人頭大。PyTorch、CUDA、cuDNN、顯存驅(qū)動(dòng)任何一個(gè)版本不匹配都會(huì)帶來(lái)莫名其妙的報(bào)錯(cuò)。我給出的建議是環(huán)境搭建優(yōu)先采用Anaconda創(chuàng)建獨(dú)立虛擬環(huán)境再安裝對(duì)應(yīng)版本的PyTorch別在系統(tǒng)環(huán)境里亂裝依賴不然訓(xùn)練數(shù)據(jù)的時(shí)候不知道為什么錯(cuò)的。還有num_workers的設(shè)置Windows系統(tǒng)需要設(shè)置成0否則多進(jìn)程加載數(shù)據(jù)會(huì)出問(wèn)題Linux系統(tǒng)在遠(yuǎn)程調(diào)試環(huán)境里也要設(shè)置成0否則可能報(bào)IPC超時(shí)錯(cuò)誤。6.4 若干易混淆類別的針對(duì)性優(yōu)化如果混淆矩陣?yán)镲@示特定兩類經(jīng)常預(yù)測(cè)錯(cuò)比如橘子和橙子在顏色紋理上很接近可以考慮幾個(gè)方向。一是增加這兩類的訓(xùn)練樣本尤其是復(fù)雜背景下的樣本二是對(duì)這兩類做專門的裁剪增強(qiáng)讓模型更多地關(guān)注局部特征三是如果特征實(shí)在過(guò)于接近可以考慮在模型輸出層增加一個(gè)“相似類”提示比如當(dāng)橘子與橙子的置信度誤差小于閾值時(shí)輸出提示讓用戶補(bǔ)充觀察其他特征比如口感與硬度但這屬于應(yīng)用層的策略在學(xué)術(shù)報(bào)告里要謹(jǐn)慎使用。7. 實(shí)際運(yùn)行結(jié)果與經(jīng)驗(yàn)總結(jié)這個(gè)項(xiàng)目跑下來(lái)我對(duì)“深度學(xué)習(xí)項(xiàng)目”這件事有了非常實(shí)際的理解。最初我也以為難點(diǎn)在模型結(jié)構(gòu)上后來(lái)發(fā)現(xiàn)真正消耗時(shí)間的是數(shù)據(jù)整理、預(yù)處理的一致性、訓(xùn)練過(guò)程中的細(xì)節(jié)調(diào)試。如果你正準(zhǔn)備做類似題目我建議你放更多精力在數(shù)據(jù)準(zhǔn)備和評(píng)估分析上別急著堆模型、調(diào)大模型先把流程跑通再一步步優(yōu)化。最后推薦試試這個(gè)擴(kuò)展方向在模型輸出的基礎(chǔ)上增加水果新鮮度、大小等級(jí)等多任務(wù)預(yù)測(cè)項(xiàng)目深度會(huì)明顯上一個(gè)大臺(tái)階。祝各位順利完成自己的設(shè)計(jì)和答辯。