戰(zhàn):從數(shù)據(jù)到模型)
1. 項(xiàng)目拆解與整體設(shè)計思路1.1 這個題目到底在做什么“基于Python-CNN機(jī)器學(xué)習(xí)深度學(xué)習(xí)的常見中草藥識別”這種課程設(shè)計題目最近幾年在各大高校里出現(xiàn)的頻率越來越高。我先用大白話把題目拆開你要做的不是“背誦一堆草藥名字”而是讓計算機(jī)通過圖片自動判斷“這是哪種中藥材”。現(xiàn)代人習(xí)慣拿手機(jī)拍照想識別路邊的車前草、廚房里的枸杞或者藥店抓回來的干黃連——而CNN這一類深度學(xué)習(xí)模型恰好是這項(xiàng)工作最好的工具。從課程設(shè)計和畢業(yè)設(shè)計的考核角度看這個項(xiàng)目有幾個硬指標(biāo)自己構(gòu)建或調(diào)優(yōu)一個CNN模型不是簡單調(diào)用現(xiàn)成接口而是能理解每一層的設(shè)計意圖。有一套合理的數(shù)據(jù)處理流程包括數(shù)據(jù)集的整理、圖像增強(qiáng)、歸一化等預(yù)處理。訓(xùn)練可得且效果可量化也就是說最終要給出準(zhǔn)確率、損失曲線、混淆矩陣這類能說服答辯老師的圖表。有實(shí)際驗(yàn)證新拍的圖片能跑通識別流程而不是只停留在訓(xùn)練集上自嗨。這個題目適合哪個階段的人做我見過三類人做它第一類是本科課程設(shè)計周期2到4周需要基礎(chǔ)功能完整、流程顯眼即可第二類是本科畢業(yè)設(shè)計周期3到5個月要加對比實(shí)驗(yàn)、可視化分析甚至要整理成小論文第三類是研究生入門項(xiàng)目重點(diǎn)在于把遷移學(xué)習(xí)、細(xì)粒度分類這些思路玩起來。不同目標(biāo)決定了你的技術(shù)選型深度文章后面會分階段說明。1.2 為什么用CNN而不是傳統(tǒng)機(jī)器學(xué)習(xí)方案放到5年前很多圖像識別項(xiàng)目用的是“手工提取特征傳統(tǒng)分類器”的路子。比如先把圖像的HOG特征、色彩直方圖、紋理特征提出來再用SVM或隨機(jī)森林做分類。這條路對中草藥識別的問題是中草藥識別恰恰是典型的“細(xì)節(jié)決定成敗”任務(wù)。兩種植物可能顏色相近、形狀相似用手工特征很難界定到底抓哪個特征才是真正的區(qū)分點(diǎn)。CNN的做法是“端到端”的——你給它看大量標(biāo)注好的圖片它不需要你告訴它該看什么部位它會自己在訓(xùn)練過程中學(xué)會選取對分類最有用的特征。第一層網(wǎng)絡(luò)可能學(xué)的是邊緣和色彩塊第二層可能學(xué)出葉片的紋理模式更深層則能學(xué)到“這個部位的形狀結(jié)合那個部位的顏色特征指向黃連”。這種自動學(xué)習(xí)特征的能力正是處理自然場景下植物照片的關(guān)鍵。以我的實(shí)操體驗(yàn)來說傳統(tǒng)方案在只有20多種藥材、每類只有幾十張圖的時候SVM勉強(qiáng)能跑到70%左右精度而同樣條件下哪怕是一個基礎(chǔ)CNN訓(xùn)練充分后也能輕松超過85%。如果數(shù)據(jù)量更大、種類更多傳統(tǒng)方案下滑得非常快CNN受到的影響卻很有限。所以這個題目選CNN不是炫技是這個任務(wù)天然適配。1.3 技術(shù)棧選型Python生態(tài)怎么搭標(biāo)題里點(diǎn)名了Python這本身沒什么好糾結(jié)的——Python在深度學(xué)習(xí)領(lǐng)域幾乎是唯一的選擇。但具體選哪些庫我覺得要根據(jù)你的硬環(huán)境和調(diào)試習(xí)慣來深度學(xué)習(xí)框架KerasTensorFlow 2.x的接口或PyTorch。課程設(shè)計優(yōu)先推薦Keras因?yàn)榻涌诜庋b度高幾行就能起一個像樣的CNN畢業(yè)設(shè)計優(yōu)先推薦PyTorch因?yàn)樽鰀ebug、自定義網(wǎng)絡(luò)結(jié)構(gòu)、看中間特征可視化都更靈活。如果你有論文需求那PyTorch是更好的選擇。圖像處理OpenCV和Pillow。數(shù)據(jù)增強(qiáng)環(huán)節(jié)兩者都要用OpenCV的插值速度快、Pillow的API更友好。數(shù)據(jù)科學(xué)工具NumPy、Pandas、Matplotlib、Seaborn。前兩個用來處理標(biāo)簽和數(shù)據(jù)劃分后兩個用于繪制loss曲線和混淆矩陣。開發(fā)環(huán)境首選Jupyter Notebook或JupyterLab逐段運(yùn)行看結(jié)果方便正式訓(xùn)練腳本建議存成.py文件提交到服務(wù)器上跑。一個容易忽略的點(diǎn)是模型訓(xùn)練的過程中會產(chǎn)生大量中間結(jié)果比如訓(xùn)練日志、模型權(quán)重文件、可視化圖像等建議從一開始就建立清晰的目錄結(jié)構(gòu)把代碼、數(shù)據(jù)、模型、結(jié)果分開存放。我自己深受“數(shù)據(jù)、代碼、結(jié)果混在一起最后答辯前找不到模型文件”的苦這個習(xí)慣非常重要。圍繞技術(shù)選型還有一個經(jīng)典問題為什么不直接用預(yù)訓(xùn)練模型跑遷移學(xué)習(xí)我覺得對課程設(shè)計來說“從零搭建一個CNN”這個動作本身往往是評分點(diǎn)。如果一上來就用ResNet的預(yù)訓(xùn)練權(quán)重模型的“理解深度”就不好展示。但畢設(shè)階段完全可以把兩者都做自己搭建的基礎(chǔ)CNN作為基線再用遷移學(xué)習(xí)構(gòu)建一個更強(qiáng)的模型作為對比這就是一個體面的實(shí)驗(yàn)設(shè)計了。2. 核心模塊原理與方案選擇2.1 CNN識別中草藥卷積層在學(xué)什么很多同學(xué)聽到CNN就怕覺得里面的數(shù)學(xué)公式嚇人。其實(shí)理解CNN只需要抓住一個邏輯它為什么能識別植物因?yàn)樗诜謱犹崛D像特征。先看卷積層。可以把卷積操作理解成拿著一個“小放大鏡”在圖像上滑動每次只看局部區(qū)域。這個放大鏡就是卷積核它提取的是“某個局部區(qū)域內(nèi)某種模式存在的強(qiáng)度”。比如一個卷積核專門用來檢測“橫著的一條深色線”另一個卷積核檢測“綠色塊之間的白色間隙”。中草藥圖片里這些模式非常關(guān)鍵——葉片邊緣的鋸齒形狀、葉脈的走向、花蕊的顏色分布本質(zhì)上都是這種局部模式在不同尺度的組合。卷積層的參數(shù)并不是人手工設(shè)置的而是通過訓(xùn)練自動調(diào)整的。起初是隨機(jī)數(shù)模型看到大量“這是薄荷葉”和“這不是薄荷葉”的圖片后自動調(diào)整那些能讓分類正確率提高的卷積核值。這就是“機(jī)器學(xué)習(xí)”中最本質(zhì)的部分特征是學(xué)出來的不是人設(shè)計出來的。然后是池化層。它干的事情是“壓縮信息”。比如一張200×200的圖片經(jīng)過一次卷積和一次2×2的池化尺寸會變成100×100。池化的意義有兩個一是降低計算量二是不怕小位移——葉子稍微歪一點(diǎn)、換個角度拍池化之后提取到的關(guān)鍵模式還在。這一點(diǎn)對中草藥識別特別重要咱們平時拍照不可能像證件照一樣規(guī)規(guī)矩矩傾斜、遮擋、光線變化是常態(tài)。到了網(wǎng)絡(luò)后面幾層模型會把前面學(xué)到的局部特征拼接成“這張圖整體上是什么風(fēng)格”的判斷最終經(jīng)過全連接層把特征映射到具體的分類概率上。輸出層通常是Softmax它會把10個或20個類別的得分變成一組和為1的概率值“概率最大的那個類別”就是識別結(jié)果。這套流程和人類看植物時“先看葉形再看葉脈再看花和整體氣質(zhì)”是一個邏輯聽著是不是一點(diǎn)都不玄了。2.2 數(shù)據(jù)的坑與數(shù)據(jù)集的構(gòu)建策略中草藥識別項(xiàng)目最大的坑不在模型在數(shù)據(jù)。這是我反復(fù)和做這個題目的同學(xué)強(qiáng)調(diào)的。規(guī)模問題CNN雖然強(qiáng)大但它是個“飯量很大”的模型。如果只給每個類別準(zhǔn)備30張圖同濟(jì)的模型再牛也會陷入過擬合——訓(xùn)練集上識別近乎100%拿到新拍的照片上就“翻車”。根據(jù)我的經(jīng)驗(yàn)做這個題目每個類別至少準(zhǔn)備100張以上圖片如果每類能達(dá)到300張訓(xùn)練效果會比較從容。數(shù)據(jù)不夠怎么辦有兩個辦法一是“數(shù)據(jù)增強(qiáng)”也就是用旋轉(zhuǎn)、翻轉(zhuǎn)、裁剪、亮度調(diào)整等方法把一張圖片變出多張來二是“遷移學(xué)習(xí)”用別人在大規(guī)模數(shù)據(jù)集上訓(xùn)練好的模型權(quán)重做初始化這樣只需少量數(shù)據(jù)也能有不錯的準(zhǔn)確率。類別選擇常見中草藥可以選10到20類。不需要選太生僻的品種因?yàn)樵缴У臄?shù)據(jù)越難找而且答辯時老師也不一定認(rèn)識效果反而不好。黃金選擇是那些特征明顯、你所在城市路邊或藥房能見到的品種薄荷、魚腥草、金銀花、薏苡仁、枸杞、陳皮、當(dāng)歸、黃連、板藍(lán)根、益母草……這些類別外觀區(qū)分度相對好適合入門。在類別設(shè)計上我強(qiáng)烈建議包含“易混淆組”。比如把“薄荷”和“荊芥”都選進(jìn)去或把“干姜”和“黃姜”放在一起。為什么因?yàn)檫@個項(xiàng)目能體現(xiàn)深度學(xué)習(xí)的價值就在于“不是簡單背答案而是真的在學(xué)區(qū)分特征”。如果選的10類藥材長得差異極大模型可能隨便學(xué)個顏色就能分類答辯時老師追問“為什么區(qū)分這兩種”你完全沒法解釋深層的特征學(xué)習(xí)。設(shè)一組易混淆類別后面做混淆矩陣、分析錯誤樣本都有了素材。數(shù)據(jù)獲取最直接的是用搜索引擎下載圖片但要注意版權(quán)問題建議只用于課程學(xué)習(xí)。更穩(wěn)妥的方案是去一些公開的植物圖像數(shù)據(jù)集或者自己用手機(jī)拍攝條件允許的話保持同一藥材的多角度、多背景、不同光照。數(shù)據(jù)集質(zhì)量判斷的標(biāo)準(zhǔn)很簡單真實(shí)場景、光線變化、有背景雜亂的照片比干凈的影棚圖更“管用”因?yàn)橛?xùn)練出來的模型在實(shí)際場景中更耐用。下載之后必須人工清理一遍把錯誤類別的圖、文字圖、重復(fù)圖全部刪掉這一步?jīng)]什么技巧就是要耐心。2.3 遷移學(xué)習(xí)為什么是中草藥識別的最優(yōu)選如果讓我對做畢設(shè)的同學(xué)說一個建議那就是別只盯著自己搭的CNN把遷移學(xué)習(xí)加進(jìn)來。比如用ImageNet上訓(xùn)練過的ResNet50作為特征提取器去掉它原來的分類層換成你自己的中草藥分類頭然后整個網(wǎng)絡(luò)在你的數(shù)據(jù)上繼續(xù)訓(xùn)練。這個方案有幾個實(shí)打?qū)嵉暮锰幨諗克俣葮O快——你用基礎(chǔ)CNN可能要訓(xùn)練50個epoch才到85%準(zhǔn)確率遷移學(xué)習(xí)10個epoch可能就93%了對數(shù)據(jù)量的要求也低很多每類只需要幾十張圖就能跑出可用的模型而且更魯棒對光照變化、模糊、遮擋的耐受性明顯更好。順帶補(bǔ)充一句其實(shí)真正厲害的圖像分類系統(tǒng)幾乎很少從零開始訓(xùn)練大模型因?yàn)閿?shù)據(jù)量和時間成本太高。用別人在數(shù)十萬張圖上練好的通用特征再在專業(yè)領(lǐng)域數(shù)據(jù)上做微調(diào)也就是所謂“微調(diào)”Fine-tuning本身就是工程實(shí)踐里的標(biāo)準(zhǔn)做法。在答辯時如果老師問“為什么選ResNet50”你可以回應(yīng)“因?yàn)樗肓藲埐钸B接解決了深層網(wǎng)絡(luò)梯度消失的問題特征復(fù)用效率高”如果問“為什么用了預(yù)訓(xùn)練權(quán)重”你可以回應(yīng)“因?yàn)轭I(lǐng)域數(shù)據(jù)集樣本量有限復(fù)用通用圖像特征能降低過擬合風(fēng)險、加快收斂”。這些邏輯自洽的表述會讓答辯的深度拉高一個檔次。關(guān)于遷移學(xué)習(xí)和基礎(chǔ)CNN的關(guān)系我不建議把二者對立起來。課程設(shè)計可以以基礎(chǔ)CNN為主遷移學(xué)習(xí)作為加分項(xiàng)畢設(shè)則可以作為對比實(shí)驗(yàn)出現(xiàn)——基礎(chǔ)CNN是一種實(shí)現(xiàn)遷移學(xué)習(xí)是另一種實(shí)現(xiàn)兩者各成一條對比路線實(shí)驗(yàn)章節(jié)自然豐富了很多。3. 從零到一的搭建與訓(xùn)練實(shí)操3.1 項(xiàng)目環(huán)境準(zhǔn)備與依賴安裝無論你是課程設(shè)計還是畢業(yè)設(shè)計我建議先用本機(jī)做小規(guī)模試驗(yàn)跑通了再考慮上服務(wù)器。環(huán)境準(zhǔn)備方面我自己最常用的一套組合是conda create -n cnn_herb python3.9 conda activate cnn_herb pip install tensorflow-cpu2.12.* # 若顯卡可用則去掉 -cpu 后綴 pip install torch torchvision # 選裝做遷移學(xué)習(xí)時使用 pip install opencv-python pillow numpy pandas matplotlib seaborn scikit-learn安裝的時候有兩點(diǎn)經(jīng)驗(yàn)第一TensorFlow和PyTorch不必都裝CPU版本如果你的電腦有NVIDIA顯卡務(wù)必裝對應(yīng)CUDA的GPU版本訓(xùn)練速度能快一個數(shù)量級第二如果pip安裝太慢直接換鏡像源pip install xxx -i https://pypi.tuna.tsinghua.edu.cn/simple這不算什么“高深技巧”但真的能讓你少等很久。環(huán)境裝好后建議先測試一下框架能不能正常調(diào)用GPUimport tensorflow as tf print(GPU available:, tf.config.list_physical_devices(GPU))如果輸出是空列表說明你的TensorFlow沒識別到顯卡接下來訓(xùn)練會很痛苦。這時候回到第一步檢查CUDA版本和tensorflow版本是否匹配。這個問題太常見了下面專門有一個小節(jié)講這里先按“能識別到GPU”繼續(xù)。3.2 數(shù)據(jù)整理與數(shù)據(jù)集劃分從亂圖到干凈數(shù)據(jù)集以最簡單的目錄結(jié)構(gòu)為例我建議把圖片按“類目-編號”命名并放在按類別命名的文件夾里dataset/ mint/ # 薄荷 mint_001.jpg mint_002.jpg ... honeysuckle/ # 金銀花 honeysuckle_001.jpg ... ...然后寫一個腳本把數(shù)據(jù)讀進(jìn)來劃分成訓(xùn)練集和驗(yàn)證集。劃分比例上常用的有8:2、7:3等。我自己偏好80%訓(xùn)練、10%驗(yàn)證、10%測試不過課程設(shè)計沒有絕對標(biāo)準(zhǔn)關(guān)鍵是驗(yàn)證集和測試集千萬不要在訓(xùn)練時被模型“看到”否則準(zhǔn)確率虛高。這是所有初學(xué)者的第一個認(rèn)知壁壘。import os, random, shutil random.seed(42) src ./dataset train_dir ./dataset_split/train val_dir ./dataset_split/val test_dir ./dataset_split/test os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) os.makedirs(test_dir, exist_okTrue) for cls in os.listdir(src): class_path os.path.join(src, cls) if not os.path.isdir(class_path): continue imgs os.listdir(class_path) random.shuffle(imgs) n_train int(len(imgs) * 0.8) n_val int(len(imgs) * 0.1) for i, img in enumerate(imgs): dst if i n_train: dst os.path.join(train_dir, cls, img) elif i n_train n_val: dst os.path.join(val_dir, cls, img) else: dst os.path.join(test_dir, cls, img) os.makedirs(os.path.dirname(dst), exist_okTrue) shutil.copy(os.path.join(class_path, img), dst)這段腳本做的事情很簡單按比例把每一類的圖片復(fù)制到三個新目錄。為什么不用移動復(fù)制更安全萬一劃分比例不對原圖還在。為什么固定了隨機(jī)種子為了保證運(yùn)行結(jié)果可復(fù)現(xiàn)答辯時老師問“你那次實(shí)驗(yàn)的結(jié)果能復(fù)現(xiàn)嗎”你只需要說一句“我們的隨機(jī)種子在實(shí)驗(yàn)設(shè)置里固定了”就很專業(yè)。接下來是標(biāo)簽編碼。如果用TensorFlow的ImageDataGenerator的flow_from_directory它會自動按文件夾名稱生成標(biāo)簽順序。如果改用自定義數(shù)據(jù)加載一般用整數(shù)標(biāo)簽從0開始遞增。標(biāo)簽順序一定要記住后面預(yù)測新圖片時要靠它把索引轉(zhuǎn)回中文名。class_labels [mint, honeysuckle, bajiao, ...] label_to_idx {name: i for i, name in enumerate(class_labels)} idx_to_label {i: name for name, i in label_to_idx.items()}3.3 圖像預(yù)處理與數(shù)據(jù)增強(qiáng)中草藥圖片的預(yù)處理分三步縮放 → 歸一化 → 增強(qiáng)??s放CNN要求輸入尺寸固定。我推薦224×224因?yàn)檫@是很多預(yù)訓(xùn)練模型的標(biāo)配尺寸兼容性好。如果你的基礎(chǔ)CNN不夠深也可以使用128×128減少計算量但圖像的細(xì)節(jié)信息會丟失一些。歸一化把像素值從0~255映射到0~1或-1~1幫助梯度穩(wěn)定、加快收斂。這一步在Keras里面寫一行from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1./255, rotation_range30, # 隨機(jī)旋轉(zhuǎn)30度 width_shift_range0.2, # 水平平移20% height_shift_range0.2, # 垂直平移20% shear_range0.2, # 剪切變換 zoom_range0.2, # 隨機(jī)縮放 horizontal_flipTrue, # 水平翻轉(zhuǎn) fill_modenearest # 填充模式 ) test_datagen ImageDataGenerator(rescale1./255)數(shù)據(jù)增強(qiáng)說白了就是“把訓(xùn)練照片增加‘變數(shù)’”。想想你真實(shí)使用軟件的場景有人把葉子拍歪了、光線太暗了、離得太近了模型如果只見過“標(biāo)準(zhǔn)圖”這些情況就會誤判。增強(qiáng)處理使模型學(xué)會對形變、亮度和位置的變化保持魯棒。需要注意驗(yàn)證集和測試集絕對不能做增強(qiáng)只能做縮放和歸一化。因?yàn)樵鰪?qiáng)是人為地制造不同樣本來擴(kuò)大模型認(rèn)識的邊界而驗(yàn)證集和測試集要反映“真實(shí)分布”必須保持原始狀態(tài)測試集上增強(qiáng)沒有任何意義。3.4 搭建基礎(chǔ)CNN模型下面是課程設(shè)計級的基礎(chǔ)CNN代碼4個卷積塊加2層全連接。這個結(jié)構(gòu)對于中草藥分類來說是“足夠用”的起始點(diǎn)from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(224, 224, 3)), MaxPooling2D(2, 2), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(2, 2), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Flatten(), Dropout(0.5), Dense(512, activationrelu), Dropout(0.3), Dense(len(class_labels), activationsoftmax) ]) model.summary()簡單解釋一下每層的選擇邏輯。第一層用了32個卷積核后面逐漸翻倍到64、128、128這是一個常見的“淺層少、深層多”設(shè)計淺層學(xué)的是細(xì)節(jié)特征多一些通道可以組合出更豐富的模式深層學(xué)的是高維語義卷積核數(shù)量不必太多以免過擬合。每個卷積塊后面跟一個MaxPooling下采樣逐步把224×224壓到14×14最后Flatten喂給全連接層。Dropout在0.5和0.3是全連接層防過擬合最重要的手段。中間用ReLU激活輸出層是Softmax多分類。3.5 訓(xùn)練參數(shù)的選擇邏輯學(xué)習(xí)率和批大小訓(xùn)練時最關(guān)鍵的幾個配置是批次大小、學(xué)習(xí)率、優(yōu)化器、Epoch數(shù)、早停策略。model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( train_generator, steps_per_epochtrain_generator.n // batch_size, epochs50, validation_dataval_generator, validation_stepsval_generator.n // batch_size, callbacks[early_stop, model_checkpoint] )批次大小我實(shí)測下來16到32是比較適合中草藥分類的。批次過小比如4會導(dǎo)致梯度震蕩太劇烈訓(xùn)練不穩(wěn)批次過大會讓每輪迭代時間變長而且顯存可能爆。如果你用的是GPU32是首選的起步值。學(xué)習(xí)率Adam的默認(rèn)學(xué)習(xí)率是0.001在多數(shù)情況下可以直接用。但如果發(fā)現(xiàn)loss在訓(xùn)練過程中反復(fù)震蕩、下不去可以先降到0.0005或0.0003重新跑一次。如果loss下降太慢可以適當(dāng)提到0.002但不建議更大很容易發(fā)散。Epoch數(shù)和早停訓(xùn)練50個epoch不一定全部跑完。設(shè)置早停監(jiān)控驗(yàn)證集loss如果連續(xù)10個epoch不再下降就自動停止同時恢復(fù)最好的權(quán)重from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) checkpoint ModelCheckpoint(herb_model.h5, monitorval_loss, save_best_onlyTrue)為什么要做早停我在這個項(xiàng)目里見過很多次訓(xùn)練集準(zhǔn)確率一路升到99%以上驗(yàn)證集卻停在85%不再動甚至開始下降。這說明模型開始“背題”而不是“學(xué)特征”了。早停就是為了對抗這種“過擬合”風(fēng)險讓模型停在泛化能力最好的那個點(diǎn)而不是訓(xùn)練最后的那個狀態(tài)。3.6 模型評估不只是看準(zhǔn)確率訓(xùn)練結(jié)束后評估部分必須做三件事驗(yàn)證集準(zhǔn)確率、loss曲線、混淆矩陣。這門課的評分老師通常最看重這三個圖。loss曲線畫法是import matplotlib.pyplot as plt plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.title(Training and Validation Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.show()如果訓(xùn)練loss和驗(yàn)證loss之間差值很大就是過擬合的直接證據(jù)如果兩者都在高位下不去說明模型容量不夠或者學(xué)習(xí)率不合適如果曲線震蕩說明批次數(shù)據(jù)分布不夠均勻可以考慮增大批次?;煜仃囉胹klearn.metrics.confusion_matrix畫出來能直觀看到哪些類別互相混淆。比如“白芷”總是被誤判為“當(dāng)歸”那你就有兩個入手方向一是增加這兩個類別的訓(xùn)練樣本二是檢查數(shù)據(jù)集本身——是不是當(dāng)時下載或標(biāo)注的時候這兩類的圖就已經(jīng)混亂了。這一步也是答辯時最能展現(xiàn)你分析能力的內(nèi)容。import numpy as np from sklearn.metrics import confusion_matrix, classification_report pred_probs model.predict(val_generator) pred_labels np.argmax(pred_probs, axis1) true_labels val_generator.classes[:len(pred_labels)] print(classification_report(true_labels, pred_labels, target_namesclass_labels))3.7 對新圖片做預(yù)測完整識別流程模型訓(xùn)練好以后識別一張真實(shí)照片的流程也要寫清楚from tensorflow.keras.preprocessing import image def predict_image(img_path): img image.load_img(img_path, target_size(224, 224)) img_array image.img_to_array(img) img_array np.expand_dims(img_array, axis0) img_array img_array / 255.0 probs model.predict(img_array)[0] idx np.argmax(probs) confidence probs[idx] return idx_to_label[idx], confidence這里面有個容易忽略的細(xì)節(jié)預(yù)測時的預(yù)處理必須和訓(xùn)練時保持完全一致。訓(xùn)練時如果做了rescale1./255預(yù)測時也要除以255否則輸入分布不一致模型表現(xiàn)會大打折扣。此外load_img默認(rèn)會做雙線性插值縮放尺寸變了但內(nèi)容語義不變這是正常操作。我的經(jīng)驗(yàn)是把這部分單獨(dú)封裝成一個腳本比如predict.py接收圖片路徑參數(shù)輸出識別結(jié)果和置信度。答辯現(xiàn)場用手機(jī)拍一張新鮮的照片程序能跑出正確結(jié)果的話現(xiàn)場效果是非常有說服力的。3.8 遷移學(xué)習(xí)模型的實(shí)戰(zhàn)補(bǔ)充上面已經(jīng)提過ResNet50遷移學(xué)習(xí)的大致思路。代碼層面是用torchvision或Keras自帶的應(yīng)用模塊加載預(yù)訓(xùn)練模型替換分類層然后凍結(jié)前面的骨干網(wǎng)絡(luò)只訓(xùn)練新的分類頭或者解凍部分層做微調(diào)。用Keras寫的話最明顯的對比實(shí)驗(yàn)代碼是from tensorflow.keras.applications import ResNet50 from tensorflow.keras.models import Model from tensorflow.keras.layers import GlobalAveragePooling2D, Dense base_model ResNet50(weightsimagenet, include_topFalse, input_shape(224, 224, 3)) base_model.trainable False # 先凍結(jié)骨干 x GlobalAveragePooling2D()(base_model.output) x Dense(256, activationrelu)(x) output Dense(len(class_labels), activationsoftmax)(x) model Model(inputsbase_model.input, outputsoutput)先只訓(xùn)練分類頭若干個epoch后可以把base_model.trainable True用極小的學(xué)習(xí)率比如0.0001整體微調(diào)幾輪效果往往還能再上一個臺階。遷移學(xué)習(xí)有一個相當(dāng)重要的問題網(wǎng)絡(luò)結(jié)構(gòu)變了輸入尺寸和歸一化方式也要跟著變。用ResNet50輸入圖片需要按ImageNet數(shù)據(jù)集的均值方差來歸一化而不是簡單除以255。偷懶就會造成特征分布不匹配導(dǎo)致準(zhǔn)確率不如基礎(chǔ)CNN的詭異情況。細(xì)節(jié)上務(wù)必查一下你所用預(yù)訓(xùn)練模型的標(biāo)準(zhǔn)預(yù)處理方式。4. 常見問題與排查技巧實(shí)錄4.1 過擬合訓(xùn)練集99%驗(yàn)證集85%我見過最多的現(xiàn)象就是這一個。模型把訓(xùn)練數(shù)據(jù)“背得滾瓜爛熟”但遇到新圖片就露怯。解決方向的優(yōu)先級如下增加數(shù)據(jù)增強(qiáng)強(qiáng)度。把旋轉(zhuǎn)范圍從20度提高到40度加入brightness_range[0.8,1.2]這類亮度擾動效果往往立竿見影。增加Dropout比例。全連接層的Dropout從0.5提到0.6或0.7或者在全連接前再加一層Dropout??s小模型容量。把卷積核數(shù)量從128降到64或32全連接層的512降到256。這也算個入門級思路很多人以為加參數(shù)就能提升精度其實(shí)小模型在數(shù)據(jù)量不夠時反而訓(xùn)得更好。數(shù)據(jù)擴(kuò)量。回到數(shù)據(jù)集增加樣本量或?qū)γ款愂占鄻踊膱D片。另外要說的是過擬合的原因首先是數(shù)據(jù)量不足。模型參數(shù)量動輒幾百萬訓(xùn)練數(shù)據(jù)才幾百張在參數(shù)空間里顯得捉襟見肘。在某些情況下數(shù)據(jù)增強(qiáng)并不能創(chuàng)造真正的新信息——旋轉(zhuǎn)、翻轉(zhuǎn)只是改變了位置和角度如果數(shù)據(jù)本身有問題可能還是會出錯。所以增加新圖片是治本增強(qiáng)是治標(biāo)。4.2 數(shù)據(jù)量不足時的救命招遷移學(xué)習(xí)更不挑數(shù)據(jù)如果你時間緊、只有每類50張圖還是有希望拿到90%以上的效果。主線就是把上面的3.8遷移學(xué)習(xí)方案走通。我在做模擬項(xiàng)目X的時候遇到過一個非常極端的設(shè)定數(shù)據(jù)集只有每類60張基礎(chǔ)CNN的準(zhǔn)確率只有78%換了ResNet50微調(diào)后漲到91%。差距就是這么明顯。原因是預(yù)訓(xùn)練模型的骨干網(wǎng)絡(luò)已經(jīng)在數(shù)百萬張圖上見過各種各樣的紋理、邊緣、形狀你只需要教它“這些特征像哪一類中草藥”而不是從零教它“什么是葉脈”。對于做畢業(yè)設(shè)計的同學(xué)我建議把這個對比做進(jìn)實(shí)驗(yàn)章節(jié)基礎(chǔ)CNN vs ResNet50微調(diào) vs VGG16微調(diào)三組在同一數(shù)據(jù)集、同一評價指標(biāo)下的準(zhǔn)確率表格這一頁的內(nèi)容比寫一千字的理論要更有說服力。要注意的是微調(diào)對比實(shí)驗(yàn)一定要控制變量同樣的數(shù)據(jù)劃分、同樣的預(yù)處理、同樣的隨機(jī)種子。否則“遷移學(xué)習(xí)效果好”這個結(jié)論就很微弱。4.3 類別不平衡有的藥材圖多有的圖少網(wǎng)上扒圖的典型后果是薄荷圖好找扒下來300多張某種不那么大眾的藥材可能只有40張。此時如果不做任何處理模型會悄悄偏向樣本量大的類別甚至認(rèn)為“不管見什么圖都猜薄荷”。處理類別不平衡有三個方法按易到難排圖像增強(qiáng)補(bǔ)齊對少數(shù)類做更強(qiáng)的旋轉(zhuǎn)、平移、光照擾動甚至做隨機(jī)裁剪后放回把它“湊”到接近多數(shù)類的數(shù)量。類別加權(quán)在訓(xùn)練時給少數(shù)類的loss乘以更大的系數(shù)。Keras里可以通過class_weight參數(shù)顯式指定from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight(balanced, classesnp.unique(y_train), yy_train) class_weight_dict {i: class_weights[i] for i in range(len(class_weights))}收集更多數(shù)據(jù)這是最笨但最有效的方法。對少數(shù)類再采集一批圖片加入訓(xùn)練集。課程設(shè)計階段我用過最多的還是第一種。復(fù)制粘貼式增強(qiáng)雖然指標(biāo)上略有虛高但在小數(shù)據(jù)量項(xiàng)目中是公認(rèn)的近似手段答辯時如實(shí)說明就可以了。4.4 新照片識別不準(zhǔn)預(yù)處理不一致首當(dāng)其沖自己訓(xùn)練完拿去測試發(fā)現(xiàn)真實(shí)環(huán)境下一塌糊涂最常見的坑是“環(huán)境變了”。訓(xùn)練用的圖片可能是搜索引擎里干凈整齊的樣本測試時你用手機(jī)在室內(nèi)自然光下拍光線、角度、背景都不一樣這屬于典型的“領(lǐng)域差異”。我總結(jié)的排查清單是輸入尺寸和歸一化方式是否與訓(xùn)練時一致。圖片壓縮是否過于嚴(yán)重是否肉眼都看不清。攝像頭拍攝的EXIF旋轉(zhuǎn)信息是否有處理OpenCV的imread有時候讀出來是橫的而訓(xùn)練時都是正的。模型是否對背景敏感——葉子放大局部拍可能比整株植物拍更好識別這也解釋了為什么很多識別App都建議“拍局部、拍特寫”。4.5 訓(xùn)練速度太慢如果你只是在本機(jī)訓(xùn)練每一輪Epoch都可能要幾分鐘訓(xùn)練50個epoch會相當(dāng)痛苦。我的建議有三個先小后大先用64×64輸入尺寸、少量數(shù)據(jù)跑通整個流程確認(rèn)代碼和參數(shù)沒問題后再切回224×224正式訓(xùn)練。用GPU如果你的顯卡是普通入門級也夠用訓(xùn)練速度能提升幾十倍。減少不必要的Epoch多數(shù)任務(wù)在驗(yàn)證集達(dá)到峰值后繼續(xù)訓(xùn)練毫無意義依靠早停能省掉一半以上的時間。4.6 模型文件保存與加載訓(xùn)練好后記得保存完整模型或權(quán)重三種方式我都用過model.save(herb_model.h5) # 保存整個模型結(jié)構(gòu)權(quán)重 model.save_weights(herb_weights.h5) # 僅保存權(quán)重 # 推薦保存完整模型預(yù)測時不需要重新構(gòu)建結(jié)構(gòu)如果是遷移學(xué)習(xí)模型最好連帶預(yù)處理方式一起記錄在一個config.json里。我見過不止一個同學(xué)來答辯時拿著一個只有文件名的.h5卻完全想不起來當(dāng)初歸一化用的尺度。規(guī)范的筆記是保障項(xiàng)目可復(fù)現(xiàn)的基本素養(yǎng)。5. 答辯展示與項(xiàng)目深化思路5.1 答辯PPT怎么講清楚這個故事很多同學(xué)項(xiàng)目做得不錯答辯卻講成流水賬先裝環(huán)境再寫代碼最后有準(zhǔn)確率。這不是講故事的方式。中草藥識別項(xiàng)目答辯建議按“為什么→怎么做→做成什么樣→還能怎么提升”的邏輯來講簡單來說就是用“問題驅(qū)動”代替“過程驅(qū)動”。引言部分講清楚中草藥識別對日常生活的意義普通人認(rèn)不全、資深藥師也有記憶盲區(qū)、移動端拍照識別是剛需。方案部分講清楚為什么選擇CNN為什么不用傳統(tǒng)圖像識別。實(shí)現(xiàn)部分講網(wǎng)絡(luò)結(jié)構(gòu)和數(shù)據(jù)準(zhǔn)備重點(diǎn)突出“你在每個環(huán)節(jié)做了什么決策、為什么”。結(jié)果部分展示準(zhǔn)確率和混淆矩陣別怕展示錯誤——反而是分析錯誤類別的過程最能體現(xiàn)項(xiàng)目的深度和思考。有一個特別實(shí)用的技巧答辯之前準(zhǔn)備一張“失敗案例圖”。挑一張識別錯的圖片分析模型為何錯并說明可以做哪些改進(jìn)。老師在答辯時最喜歡問的就是“你這個項(xiàng)目有什么不足”這個問題答得好是很大的加分項(xiàng)。5.2 可擴(kuò)展方向從固定分類到開放識別識別固定40類藥材只是課程設(shè)計的及格線。如果你有興趣做得更深入我列出幾個可擴(kuò)展方向從分類到檢測目標(biāo)檢測模型如Faster R-CNN、YOLO可以從復(fù)雜圖片中把藥材區(qū)域框出來再分類適配的實(shí)際場景更貼近“田間拍攝、自然場景找藥材”。從單圖到多圖融合不同藥材可能在根、莖、葉、花上差異明顯可以把多個部位圖輸入一個多路網(wǎng)絡(luò)做綜合判斷。從圖片到文本識別結(jié)果結(jié)合藥材知識庫輸出“性味歸經(jīng)、功效、用法、禁忌”直接做成一個問答式的中藥知識助手。那時候項(xiàng)目的實(shí)用性和交互性會大大提升。部署到移動端用TensorFlow Lite的轉(zhuǎn)換把模型部署到手機(jī)端或嵌入式端。課程設(shè)計到這一步已經(jīng)屬于“向工程實(shí)踐靠攏”的加分項(xiàng)。半監(jiān)督學(xué)習(xí)網(wǎng)上大量弱標(biāo)注數(shù)據(jù)未充分利用可用偽標(biāo)簽或?qū)Ρ葘W(xué)習(xí)的方法挖掘未標(biāo)注數(shù)據(jù)的價值。對本科階段而言能完成其中任意一個擴(kuò)展方向并跑通演示就已經(jīng)超過絕大多數(shù)“為了交差”的同類項(xiàng)目了。5.3 關(guān)于評分標(biāo)準(zhǔn)的一點(diǎn)個人判斷不同學(xué)校、不同老師的評分偏好差異極大但有幾個共性要素幾乎是公認(rèn)的流程完整度數(shù)據(jù)處理→訓(xùn)練→評估→預(yù)測的閉環(huán)、規(guī)范程度代碼注釋、目錄結(jié)構(gòu)、實(shí)驗(yàn)記錄、可視化曲線圖、混淆矩陣、真實(shí)圖片演示、分析深度基于結(jié)果能提出改進(jìn)方向。這四個要素抓好了分?jǐn)?shù)都不會差。不過我也想潑一盆冷水課程設(shè)計階段不要追求遙遙領(lǐng)先的技術(shù)指標(biāo)。你的目標(biāo)是“證明自己掌握了基礎(chǔ)原理和工程流程”而不是“訓(xùn)練出比論文SOTA更好的模型”。項(xiàng)目周期有限的情況下把基礎(chǔ)CNN調(diào)優(yōu)到85%以上配合遷移學(xué)習(xí)對比已經(jīng)是很扎實(shí)的工作量了。與其花大量時間無節(jié)制地調(diào)參堆疊不如把精力放在把項(xiàng)目里已有的成果講透、把特征可視化的圖做漂亮。6. 寫在最后的實(shí)操經(jīng)驗(yàn)做這個題目最容易讓初學(xué)者誤判的是“時間都花在模型上”。但以我的實(shí)際經(jīng)歷來看這類圖像識別項(xiàng)目最大的時間黑洞永遠(yuǎn)是數(shù)據(jù)整理——下載、清洗、重命名、統(tǒng)一格式、調(diào)整增強(qiáng)參數(shù)。曾經(jīng)有一次我在圖書館連著蹲了一個周末凈收益只是把某個類別的“廢圖”從兩百張里刪到一百三十張但這直接拉升了模型近4個百分點(diǎn)的驗(yàn)證集準(zhǔn)確率。所以你如果問我要一條核心建議那就是數(shù)據(jù)質(zhì)量決定模型上限調(diào)參只決定模型能否接近這個上限?;ㄔ跀?shù)據(jù)上的功夫永遠(yuǎn)不吃虧。另外項(xiàng)目過程記錄很關(guān)鍵。我給所有做課程設(shè)計的同學(xué)一個通用建議從拿到題目的第一天起建立兩個文檔一個叫《實(shí)驗(yàn)日志》另一個叫《問題清單》。每天記錄做了什么操作、得到什么結(jié)果每遇到一個報錯或異常就把現(xiàn)象、排查步驟、最終解法記下來。答辯時老師問“你遇到過什么問題嗎”你隨手就能舉出一個完整的排查經(jīng)歷比“沒遇到什么問題訓(xùn)練很順利”這種回答可信度高得多。而且寫畢業(yè)設(shè)計論文時這些日志會變成你“第三章實(shí)驗(yàn)”和“第四章結(jié)果分析”最現(xiàn)成的素材寫起來輕松好幾倍。最后再分享一個小技巧訓(xùn)練完成后給你的模型取幾個不同的版本名字——比如“v1_baseline_cnn”、“v2_resnet50_finetune”、“v3_augmented”——按時間存好。做實(shí)驗(yàn)最忌諱的是“這個h5文件是剛才調(diào)過參的嗎還是昨天那個”版本管理做得越清楚后面越省事。這套方法不止適用于中草藥識別任何以“訓(xùn)練模型”為核心的課程設(shè)計或畢業(yè)設(shè)計項(xiàng)目都能直接用上。