戰(zhàn):從CNN訓(xùn)練到界面部署全流程解析)
簡介基于卷積神經(jīng)網(wǎng)絡(luò)的柑橘成熟度識別項(xiàng)目為學(xué)習(xí)PyTorch圖像分類的開發(fā)者提供完整可運(yùn)行的代碼與真實(shí)標(biāo)注數(shù)據(jù)。壓縮包共120個文件包括113張JPG與1張JPEG柑橘圖片、3個Python腳本、3個TXT說明文件整體約10.8MB。代碼實(shí)現(xiàn)從數(shù)據(jù)集預(yù)處理短邊補(bǔ)灰邊轉(zhuǎn)為正方形、隨機(jī)旋轉(zhuǎn)增強(qiáng)、生成圖片路徑與標(biāo)簽文本到訓(xùn)練卷積模型并保存權(quán)重還帶有PyQt交互界面便于直觀查看識別效果。附帶的requirement.txt可指導(dǎo)環(huán)境搭建適合作為圖像分類入門或農(nóng)業(yè)目標(biāo)識別的小型完整項(xiàng)目參考。目前已有235人學(xué)習(xí)瀏覽有助于快速掌握數(shù)據(jù)增強(qiáng)、訓(xùn)練流程與簡單界面部署的綜合實(shí)踐。1. 柑橘成熟度識別一個帶數(shù)據(jù)集的 PyTorch 卷積神經(jīng)網(wǎng)絡(luò)實(shí)戰(zhàn)包養(yǎng)過柑橘的人都知道成熟度判斷是個靠老師傅眼力的活而移植到計(jì)算機(jī)視覺里本質(zhì)就是一個圖像二分類問題Healthy 還是 Greening。這次拆的資源是一份完整的 PyTorch 分類項(xiàng)目壓縮包里面帶了三個可運(yùn)行腳本和一批真實(shí)柑橘圖片打通了「圖片路徑生成 → CNN 模型訓(xùn)練 → PyQt 界面識別」全流程。學(xué)完卷積神經(jīng)網(wǎng)絡(luò)基礎(chǔ)但沒跑過完整項(xiàng)目的初學(xué)者或者想快速在畢設(shè)里落地一個識別系統(tǒng)的同學(xué)用它比從零搭框架省不少事。關(guān)鍵是數(shù)據(jù)集、預(yù)處理和界面都齊了解壓后照著跑就能看到訓(xùn)練曲線和識別結(jié)果后面再談?wù){(diào)參和部署。2. 目錄與數(shù)據(jù)三個腳本一條線先看清數(shù)據(jù)集里的增強(qiáng)殘留訓(xùn)練再玄學(xué)也得先搞清楚手里有什么。這份資源的核心線索是三個 Python 文件加一個requirement.txt數(shù)據(jù)流方向是「讀圖片 → 生成文本標(biāo)注 → 訓(xùn)練出模型 → 界面加載模型做推理」。先別急著跑把目錄結(jié)構(gòu)和數(shù)據(jù)分布摸一遍后面能少踩一半坑。2.1 三個腳本、三個階段的數(shù)據(jù)流解壓后大致結(jié)構(gòu)如下具體名字以實(shí)際為準(zhǔn)我這里按資源描述整理柑橘成熟度識別/ ├── 01數(shù)據(jù)集文本生成制作.py ├── 02深度學(xué)習(xí)模型訓(xùn)練.py ├── 03pyqt_ui界面.py ├── requirement.txt └── 數(shù)據(jù)集/ ├── Healthy/ │ ├── Healthy (9).jpg │ ├── Healthy (9)_rotated45.jpg │ └── Healthy (9)_flip.jpg └── Greening/ ├── Greening (1).jpeg └── ...三個腳本各管一段01數(shù)據(jù)集文本生成制作.py負(fù)責(zé)掃描數(shù)據(jù)集目錄把每張圖片的路徑和類別標(biāo)簽寫進(jìn) txt02深度學(xué)習(xí)模型訓(xùn)練.py讀取 txt 劃分訓(xùn)練集和驗(yàn)證集開始訓(xùn)練并保存模型03pyqt_ui界面.py加載訓(xùn)練產(chǎn)物把模型包裝成一個能選圖、點(diǎn)按鈕、出結(jié)果的界面程序。這設(shè)計(jì)的思路是數(shù)據(jù)與模型解耦——想換數(shù)據(jù)集只改 01 腳本的掃描目錄想換網(wǎng)絡(luò)結(jié)構(gòu)只動 02 腳本中間模型定義的部分界面和訓(xùn)練互不干擾。對新手來說這種分步式結(jié)構(gòu)比一個大而全的 main.py 好調(diào)試得多哪一步出錯能直接定位到文件。2.2 Healthy 與 Greening從文件名讀類別數(shù)據(jù)集的類別目錄很直白Healthy 和 Greening 兩個文件夾類別名即標(biāo)簽。文件里的一些命名規(guī)律值得注意文件名片段含義備注Healthy (9).jpg原始健康果圖片常規(guī) jpgHealthy (9)_rotated45.jpg原圖旋轉(zhuǎn) 45 度后的增強(qiáng)樣本已帶增強(qiáng)痕跡Healthy (9)_flip.jpg原圖水平翻轉(zhuǎn)副本已帶增強(qiáng)痕跡Greening (1).jpeg黃化/病征果原始圖注意是 jpeg 后綴這里有個容易被忽略的信號數(shù)據(jù)目錄里已經(jīng)混入了_rotated45、_flip這類增強(qiáng)副本。后面跑 01 腳本時如果你不做過濾這些副本會被當(dāng)作獨(dú)立圖片再次掃描甚至再次增強(qiáng)造成訓(xùn)練集和驗(yàn)證集之間有重復(fù)樣本指標(biāo)虛高。稍后避坑章節(jié)我會專門展開這個問題。另一個實(shí)際問題是后綴不統(tǒng)一有.jpg也有.jpeg。如果腳本里 glob 只寫了*.jpgGreening 文件夾里的 jpeg 會被漏讀你訓(xùn)練集的樣本數(shù)會莫名其妙少一截。2.3 環(huán)境安裝requirement.txt 和「先建環(huán)境再裝包」環(huán)境是這類項(xiàng)目卡住新手的頭號原因。資源里帶了requirement.txt說明作者已經(jīng)幫你把依賴列表整理好了安裝的核心就一句話conda create -n citrus python3.8 -y conda activate citrus pip install -r requirement.txt第一行創(chuàng)建一個干凈的 conda 環(huán)境避免和系統(tǒng) Python 里已有的包互相污染第二行激活環(huán)境第三行按依賴清單一次性裝完。裝好后建議立刻驗(yàn)證關(guān)鍵包能不能正常導(dǎo)入python -c import torch, cv2, numpy; print(torch.__version__, cv2.__version__)如果 cv2 導(dǎo)入報(bào)錯或 torch 版本輸出異常優(yōu)先檢查 pip 源是否可用或者把 requirement.txt 里的 torch 版本和當(dāng)前顯卡驅(qū)動對照一下。CPU 機(jī)器也能跑這個項(xiàng)目只是訓(xùn)練慢一些不影響推理演示。原資源里如果附帶免安裝環(huán)境包那是給實(shí)在裝不上依賴的人準(zhǔn)備的我更推薦自己配環(huán)境因?yàn)楹竺婺愀拇a、加包遲早要學(xué)會管理依賴靠別人打包好的環(huán)境終歸是黑匣子。3. 預(yù)處理短邊補(bǔ)灰邊與旋轉(zhuǎn)擴(kuò)增01 腳本的邊界條件預(yù)處理決定了模型能學(xué)到什么也決定了天花板。這個項(xiàng)目在預(yù)處理上做了兩件典型的事把非正方形圖片通過補(bǔ)灰邊變成正方形再用旋轉(zhuǎn)和翻轉(zhuǎn)擴(kuò)增數(shù)據(jù)集。這兩步看起來簡單里面的取舍和坑都不少。3.1 為什么補(bǔ)灰邊而不是拉伸成正方形卷積神經(jīng)網(wǎng)絡(luò)的輸入通常是固定尺寸比如 224×224 或 256×256。最粗暴的做法是把任意尺寸圖片直接resize(224, 224)但這樣會破壞長寬比——本來橢圓的柑橘被壓成圓形或者細(xì)長葉片被拉寬模型很容易把「畸變后的形狀」當(dāng)成類別特征而不是真正學(xué)到果實(shí)本身的紋理和顏色。常見做法是先在短邊對稱補(bǔ)邊讓圖變成正方形再整體 resize 到網(wǎng)絡(luò)輸入尺寸?;疫吺侵行灾导炔幌窈谶吥菢右氪笃阒祬^(qū)域也不像白邊那樣可能拉高整體亮度統(tǒng)計(jì)。補(bǔ)邊時用 PIL 的ImageOps.expand非常方便from PIL import Image, ImageOps def pad_to_square(img): 把非正方形圖片按短邊補(bǔ)灰邊返回正方形圖 w, h img.size if w h: return img # 計(jì)算四個方向的補(bǔ)邊寬度灰邊值用 128 if w h: top bottom (w - h) // 2 left right 0 else: left right (h - w) // 2 top bottom 0 return ImageOps.expand(img, border(left, top, right, bottom), fill128)border參數(shù)順序是左、上、右、下很多人第一次會寫反。fill128是中性灰既非純黑也非純白對歸一化后的分布影響最小。如果圖片原本就是正方形函數(shù)直接返回不做處理這段邏輯和資源描述完全一致。3.2 旋轉(zhuǎn)擴(kuò)增的度數(shù)與重復(fù)樣本問題數(shù)據(jù)擴(kuò)增是為了讓模型見更多變體緩解小數(shù)據(jù)集的過擬合。旋轉(zhuǎn) 45 度、水平翻轉(zhuǎn)是圖像分類里最常規(guī)的擴(kuò)增手段對柑橘這種目標(biāo)居中的圖片45 度旋轉(zhuǎn)不會把目標(biāo)旋出畫面翻轉(zhuǎn)也不會引入語義歧義——健康果翻過來還是健康果。擴(kuò)增的倍數(shù)大概是這樣的關(guān)系1 張?jiān)紙D可以衍生出旋轉(zhuǎn) 45 度副本、翻轉(zhuǎn)副本再加上可能的組合變換最后訓(xùn)練集規(guī)模能擴(kuò)大到原來的 3 到 5 倍。但擴(kuò)增不是越猛越好旋轉(zhuǎn) 90 度以上時果實(shí)的朝向已經(jīng)完全改變?nèi)绻鎸?shí)場景里攝像頭很少拍倒置圖這種樣本反而會干擾模型。這份資源比較特別的地方在于數(shù)據(jù)目錄里已經(jīng)帶了_rotated45和_flip后綴的成品也就是說增強(qiáng)副本已經(jīng)在文件層面存在了。這時候運(yùn)行 01 腳本要格外小心如果腳本會把目錄里所有圖片都讀一遍并再次做旋轉(zhuǎn)/翻轉(zhuǎn)等于把增強(qiáng)樣本又增強(qiáng)一遍數(shù)據(jù)分布會嚴(yán)重偏向某些源圖驗(yàn)證集還容易泄漏。拿到代碼后先打開 01 腳本看它的掃描邏輯確認(rèn)它是否按文件名后綴排除了已有增強(qiáng)副本。3.3 01 腳本解析讀路徑、補(bǔ)邊、寫標(biāo)簽這類「數(shù)據(jù)集文本生成」腳本的邏輯大同小異核心就是遍歷類別目錄、整理路徑和標(biāo)簽、按比例切分訓(xùn)練驗(yàn)證集、寫入 txt。它的框架大概是這樣的import os import random from PIL import Image, ImageOps data_root 數(shù)據(jù)集 output_train train.txt output_val val.txt class_names [d for d in os.listdir(data_root) if os.path.isdir(os.path.join(data_root, d))] class_to_id {name: idx for idx, name in enumerate(class_names)} # 比如 {Greening: 0, Healthy: 1} all_lines [] for cls in class_names: cls_dir os.path.join(data_root, cls) for fn in os.listdir(cls_dir): # 注意這里要同時匹配 jpg/jpeg過濾 _rotated45/_flip 增強(qiáng)副本 if not (fn.endswith(.jpg) or fn.endswith(.jpeg)): continue if _rotated45 in fn or _flip in fn: continue all_lines.append(f{os.path.join(cls_dir, fn)} {class_to_id[cls]}\n) random.shuffle(all_lines) split_idx int(len(all_lines) * 0.8) # 常見 8:2 劃分 with open(output_train, w) as f: f.writelines(all_lines[:split_idx]) with open(output_val, w) as f: f.writelines(all_lines[split_idx:])標(biāo)簽用數(shù)字編碼而不是直接寫類別名是因?yàn)?PyTorch 的損失函數(shù)只認(rèn)整數(shù)索引你需要在訓(xùn)練時用一個class_names列表把索引映射回名字。8:2 是常見的劃分比例圖片總量少的項(xiàng)目可以考慮 7:3但驗(yàn)證集最少也得留出幾十張不然準(zhǔn)確率波動太大沒法判斷模型好壞。這一步的產(chǎn)出是 txt 文件每行左邊是圖片絕對路徑或相對路徑右邊是標(biāo)簽數(shù)字。后面 02 腳本會逐行讀這個文件。建議生成后順手打開看幾行確認(rèn)路徑存在、標(biāo)簽和類別對應(yīng)別等訓(xùn)練時報(bào)了文件不存在才回頭查。4. 訓(xùn)練02 腳本的讀圖、參數(shù)與模型落盤位置訓(xùn)練腳本是這整套項(xiàng)目的核心。它要把 txt 里的路徑變成張量把張量喂進(jìn)卷積神經(jīng)網(wǎng)絡(luò)算損失、回傳梯度最后把學(xué)到的權(quán)重存成本地文件。這一段我們拆開看數(shù)據(jù)讀取、網(wǎng)絡(luò)選型和參數(shù)設(shè)置再說模型保存路徑的細(xì)節(jié)。4.1 DataLoader 與訓(xùn)練/驗(yàn)證劃分02 腳本拿到 01 生成的 txt 后第一步是解析文本第二步是用 PyTorch 的Dataset和DataLoader封裝數(shù)據(jù)from torch.utils.data import Dataset, DataLoader from PIL import Image from torchvision import transforms class CitrusDataset(Dataset): def __init__(self, txt_path, transformNone): self.samples [] with open(txt_path, r, encodingutf-8) as f: for line in f: path, label line.strip().split( ) self.samples.append((path, int(label))) self.transform transform def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] img Image.open(path).convert(RGB) if self.transform: img self.transform(img) return img, label transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_loader DataLoader(CitrusDataset(train.txt, transform), batch_size16, shuffleTrue, num_workers2) val_loader DataLoader(CitrusDataset(val.txt, transform), batch_size16, shuffleFalse, num_workers2)Resize((224, 224))因?yàn)榍懊嬉呀?jīng)補(bǔ)成正方形了所以這里 resize 不會再造成畸變。Normalize的參數(shù)是 ImageNet 統(tǒng)計(jì)出來的均值和標(biāo)準(zhǔn)差用了預(yù)訓(xùn)練權(quán)重就得配這套歸一化參數(shù)否則特征分布對不上。shuffleTrue只用在訓(xùn)練集驗(yàn)證集不打亂方便每次評估看到一致的順序。這個自定義 Dataset 的寫法是 PyTorch 標(biāo)準(zhǔn)模板核心就兩個方法__len__告訴迭代器總共有多少樣本__getitem__根據(jù)索引返回一個(圖片張量, 標(biāo)簽)對。讀 txt 時用strip().split( )是按「路徑 空格 標(biāo)簽」的結(jié)構(gòu)解析如果 01 腳本寫的是\t分隔這里要相應(yīng)改成split(\t)。4.2 網(wǎng)絡(luò)、損失與訓(xùn)練參數(shù)怎么設(shè)資源標(biāo)題寫的是「基于卷積神經(jīng)網(wǎng)絡(luò)」具體網(wǎng)絡(luò)結(jié)構(gòu)以代碼里為準(zhǔn)——常見的選擇是 ResNet18、ResNet34 或一個自定義的小型 CNN。用預(yù)訓(xùn)練 ResNet 做遷移學(xué)習(xí)在小數(shù)據(jù)集上效果最好因?yàn)楦涕賵D片紋理和 ImageNet 里大量自然圖像的特征分布接近模型不需要從頭學(xué)邊緣和紋理基元。參數(shù)建議值說明輸入尺寸224×224ResNet 等結(jié)構(gòu)的標(biāo)準(zhǔn)輸入batch_size16 或 32顯存不夠就降到 8epoch50 到 100小數(shù)據(jù)集 50 輪基本收斂學(xué)習(xí)率1e-3 起Adam 配 1e-3SGD 配 1e-2優(yōu)化器Adam 或 SGDAdam 收斂快SGD 上限高損失函數(shù)CrossEntropyLoss二分類多分類通用二分類可以用CrossEntropyLoss它內(nèi)部自帶 softmax網(wǎng)絡(luò)最后一層直接輸出兩個類別的 logits 就行不要在損失函數(shù)前手動接 softmax否則梯度會疊加出問題。學(xué)習(xí)率的經(jīng)驗(yàn)值是用預(yù)訓(xùn)練權(quán)重時特征提取部分設(shè)小一點(diǎn)比如 1e-4新加的分類頭設(shè)大一點(diǎn)1e-3可以用分組學(xué)習(xí)率的寫法但新手階段統(tǒng)一 1e-3 也沒問題。訓(xùn)練循環(huán)里除了算 loss至少要打印每一輪的train_loss和val_acc。如果 val_acc 在 70 輪后還在漲說明沒收斂完繼續(xù)加輪數(shù)如果 train_loss 一直降但 val_acc 不動甚至下降典型的過擬合這時候應(yīng)該加擴(kuò)增或把模型換小。4.3 模型保存與加載pth 文件別亂動訓(xùn)練完成后保存模型是最后一個關(guān)鍵動作。PyTorch 有兩種保存方式整個模型或僅狀態(tài)字典常見做法是保存state_dict# 訓(xùn)練完后保存 torch.save(model.state_dict(), citrus_model.pth) # 推理時加載 model ResNet18(num_classes2) model.load_state_dict(torch.load(citrus_model.pth, map_locationcpu)) model.eval()map_locationcpu這行至關(guān)重要。很多人訓(xùn)練用 GPU加載時機(jī)器沒 GPU 或 CUDA 版本不對直接torch.load會報(bào)類似No such operator的錯。顯式指定map_location能把權(quán)重先搬到 CPU再按需轉(zhuǎn)回 GPU。加載后記得調(diào)用model.eval()把 dropout 和 BN 層切成推理模式否則同一張圖每次預(yù)測結(jié)果可能不一樣。模型文件建議放在項(xiàng)目根目錄或者單獨(dú)的weights/文件夾里別塞到數(shù)據(jù)集目錄里。03 界面腳本默認(rèn)會按相對路徑找模型文件你挪了位置界面就加載失敗。這份資源訓(xùn)練好的模型是保存在本地的也就是說你可以隨時訓(xùn)練出自己的版本替換它。5. 避坑五個讓指標(biāo)虛高的數(shù)據(jù)與部署陷阱這類帶數(shù)據(jù)集的 PyTorch 項(xiàng)目跑通本身不難難的是跑出來的指標(biāo)是真實(shí)的。下面五條踩坑記錄有的是這個數(shù)據(jù)目錄結(jié)構(gòu)直接導(dǎo)致的有的是同類項(xiàng)目里反復(fù)出現(xiàn)的通病每條按「現(xiàn)象 → 原因 → 解決」展開。5.1 增強(qiáng)副本泄漏到驗(yàn)證集指標(biāo)虛高訓(xùn)練完看到 val_acc 99.8%心里剛美一下拿幾張開微博上隨便找的柑橘圖一測識別效果一塌糊涂。這通常是數(shù)據(jù)泄漏。這個數(shù)據(jù)集里已經(jīng)存在大量_rotated45、_flip后綴的增強(qiáng)副本如果 01 腳本掃描時把它們也算作獨(dú)立樣本同一個源圖的旋轉(zhuǎn)版和翻轉(zhuǎn)版很可能同時出現(xiàn)在 train.txt 和 val.txt 里。模型等于提前見過答案驗(yàn)證集準(zhǔn)確率自然高得離譜。解決方法是掃描時按文件名后綴過濾只把不含增強(qiáng)標(biāo)記的原始圖納入劃分。更穩(wěn)妥的做法是按「源圖」去重把同一源圖衍生的所有樣本放進(jìn)同一個集合要么全在訓(xùn)練集要么全在驗(yàn)證集。拿到代碼后先確認(rèn)作者有沒有做這一步?jīng)]有就自己加一行判斷。5.2 灰邊被模型當(dāng)成特征旋轉(zhuǎn)越多越糟模型訓(xùn)練時 loss 降得很快但可視化預(yù)測結(jié)果發(fā)現(xiàn)模型判斷依據(jù)不是果實(shí)顏色和紋理而是圖片邊緣有沒有灰邊——帶灰邊的圖被歸到某一類。原因在于補(bǔ)灰邊操作對類別分布產(chǎn)生了系統(tǒng)性偏差如果 Greening 類圖片普遍是橫構(gòu)圖、補(bǔ)邊量大Healthy 類多是正方形近景圖網(wǎng)絡(luò)會偷懶學(xué)習(xí)「有灰邊 Greening」這種低成本特征。解決方法是讓補(bǔ)邊和擴(kuò)增順序配合好先做旋轉(zhuǎn)裁切再補(bǔ)邊或者補(bǔ)邊時隨機(jī)用 115 到 140 之間的灰度值填充讓灰邊不是一個固定常量。還有一個思路是干脆用中心裁剪配合 resize雖然會損失一點(diǎn)邊緣信息但至少不會引入固定偽影。5.3 jpg 和 jpeg 混放訓(xùn)練集悄悄變少訓(xùn)練日志里顯示的樣本總數(shù)比數(shù)據(jù)集里的圖片數(shù)量少或者訓(xùn)練時報(bào)FileNotFoundError仔細(xì)一看路徑指向的是.jpeg文件而掃描邏輯只匹配了.jpg。這份資源里 Healthy 類是 jpgGreening 類里出現(xiàn)了 jpeg后綴不統(tǒng)一是打包數(shù)據(jù)的常態(tài)。掃描時用多后綴匹配直接避開這個坑if not (fn.lower().endswith(.jpg) or fn.lower().endswith(.jpeg) or fn.lower().endswith(.png)): continue另外 Windows 和 Linux 對文件名大小寫敏感度不同Healthy.JPG在 Linux 下用*.jpg匹配不到統(tǒng)一轉(zhuǎn)小寫再判斷最保險(xiǎn)。5.4 顯存溢出或訓(xùn)練中斷訓(xùn)練跑到一半進(jìn)程被殺或者直接報(bào)CUDA out of memory。常見原因有三個batch_size 太大、圖片resize后分辨率還是偏高、num_workers設(shè)置過多導(dǎo)致內(nèi)存讀寫壓力大。這個數(shù)據(jù)集圖片本身尺寸不會太大但如果你自己加圖片有些手機(jī)拍的原圖是 4000×3000不進(jìn) resize 直接進(jìn)網(wǎng)絡(luò)一張圖就能吃掉幾百兆顯存。解決順序是先把圖片尺寸壓到 224×224 或 256×256再降 batch_size 到 8最后把num_workers降到 0 或 2。如果還是崩檢查是不是 PyTorch 版本和顯卡驅(qū)動不匹配這個屬于環(huán)境問題重裝對應(yīng)版本的 torch 比硬調(diào)代碼效率高。5.5 PyQt 界面加載不出圖或顏色不對03 腳本跑起來窗口正常但點(diǎn)選圖片后界面黑屏、或者柑橘顏色變得發(fā)青發(fā)藍(lán)。這是經(jīng)典的通道順序問題OpenCV 讀圖是 BGR 順序而 QImage 和 PIL 都是 RGB直接互傳就會顏色錯亂。如果界面腳本用了 cv2 讀圖再轉(zhuǎn) QImage必須加一步轉(zhuǎn)換rgb_img cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB)另外一個隱蔽問題是路徑含中文或空格。資源解壓路徑如果帶中文文件夾名PyQt 的文件對話框和 PIL 在部分 Windows 環(huán)境下會讀取失敗。把項(xiàng)目放在純英文路徑下運(yùn)行能省掉一堆莫名其妙的文件訪問異常。6. 驗(yàn)證與進(jìn)階離線盲測、批量推理與一個收尾習(xí)慣模型訓(xùn)練完、界面能出結(jié)果項(xiàng)目只能算「跑通」還不能算「可信」。接下來要做的驗(yàn)證有三個動作。第一盲測。從網(wǎng)上找或者用自己的手機(jī)拍 10 到 20 張這個項(xiàng)目沒見過的柑橘圖不要是數(shù)據(jù)集里的原圖和增強(qiáng)副本逐張喂給模型看結(jié)果。這一步能直接暴露指標(biāo)虛高的問題。第二看混淆矩陣統(tǒng)計(jì) Healthy 被錯判成 Greening 多少張、反向多少張如果某一類錯判特別多說明那一類的特征沒學(xué)到。第三對同一張圖做亮度擾動和輕微旋轉(zhuǎn)看預(yù)測結(jié)果是否穩(wěn)定不穩(wěn)定說明模型對光照和角度太敏感部署到真實(shí)環(huán)境會翻車。如果想繞開界面直接快速驗(yàn)證我習(xí)慣寫一個十行左右的批量推理腳本import torch from PIL import Image from torchvision import transforms model load_model() # 復(fù)用訓(xùn)練時的網(wǎng)絡(luò)結(jié)構(gòu)和權(quán)重 transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) for img_path in test_images: img transform(Image.open(img_path).convert(RGB)).unsqueeze(0) pred model(img).argmax(dim1).item() print(img_path, class_names[pred])這套腳本可以做批量回放測試也能用來統(tǒng)計(jì)誤判案例。從那以后我每次拿到這類打包資源第一件事不是雙擊跑 01 腳本而是先掃一遍文件后綴、看腳本里的 glob 規(guī)則、確認(rèn)增強(qiáng)副本有沒有泄漏到驗(yàn)證集再開始訓(xùn)練。這套檢查流程幫我避開了很多次「指標(biāo)看著很美、實(shí)際沒法用」的尷尬。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取