花系統(tǒng)從零搭建:深度學(xué)習(xí)圖像分類與遷移學(xué)習(xí)實(shí)戰(zhàn))
從零搭一個(gè)能用的智慧識(shí)花系統(tǒng)設(shè)計(jì)思路、訓(xùn)練細(xì)節(jié)和那些坑我今年花了不少時(shí)間做了一個(gè)基于深度學(xué)習(xí)的智慧識(shí)花系統(tǒng)從數(shù)據(jù)集準(zhǔn)備、模型訓(xùn)練、后端接口到前端展示全流程走了一遍。這篇東西不是來(lái)秀架構(gòu)圖的而是把我踩過(guò)的坑、想明白的道理、以及最終能跑通的方案一步步寫出來(lái)。不管你是拿它做畢業(yè)設(shè)計(jì)、課設(shè)還是純粹想做一個(gè)能識(shí)別花的小工具都可以參考這套思路。先把這個(gè)系統(tǒng)是什么說(shuō)清楚你拍一張花的照片傳上去系統(tǒng)告訴你這是什么花、大概置信度多少、還能展示一些基本信息。后端用卷積神經(jīng)網(wǎng)絡(luò)做圖像分類前端支持單張圖片上傳和實(shí)時(shí)預(yù)覽整套代碼不是玩具是能夠容納幾百個(gè)類別、具備工程化改造空間的項(xiàng)目。適合誰(shuí)來(lái)讀如果你剛?cè)腴T深度學(xué)習(xí)想找一個(gè)圖像分類方向練手這篇文章能幫你建立完整的項(xiàng)目視角如果你是正在做畢業(yè)設(shè)計(jì)需要寫系統(tǒng)設(shè)計(jì)和萬(wàn)字論文后面關(guān)于報(bào)告結(jié)構(gòu)的章節(jié)可以直接套用哪怕你只是好奇識(shí)花是怎么實(shí)現(xiàn)的看完也能明白核心原理不會(huì)被花里胡哨的詞匯繞暈。1. 整體設(shè)計(jì)思路拆解1.1 核心需求解析識(shí)花系統(tǒng)到底要解決什么問(wèn)題做任何一個(gè)項(xiàng)目第一步不是選技術(shù)而是把需求拆干凈。智慧識(shí)花系統(tǒng)表面上就一個(gè)動(dòng)作——拍照告訴我是啥花但拆開來(lái)看有幾個(gè)隱性的要求。第一是識(shí)別的準(zhǔn)確性?;ê突ㄖg差別有時(shí)候特別小比如兩三種菊花放在一起顏色、花瓣形態(tài)都很接近人眼都要湊近看模型要是分錯(cuò)用戶立刻覺(jué)得這系統(tǒng)不行。第二是響應(yīng)速度。你不可能讓用戶對(duì)著一張照片等十秒鐘才出結(jié)果識(shí)別接口需要控制在幾百毫秒到一兩秒的量級(jí)。第三是易用性。上傳、識(shí)別、展示結(jié)果三步以內(nèi)要完成前端不能堆太多操作邏輯。第四是擴(kuò)展性。今天能識(shí)別50種花明天可能要加到200種、500種系統(tǒng)的結(jié)構(gòu)不能一加數(shù)據(jù)就推倒重來(lái)。這些需求推導(dǎo)到技術(shù)選型上就非常清晰圖像分類任務(wù)必然用深度卷積神經(jīng)網(wǎng)絡(luò)要在速度和精度之間平衡不能一味追求最大的模型類別的添加依賴于數(shù)據(jù)集的擴(kuò)充所以數(shù)據(jù)管線必須規(guī)范接口設(shè)計(jì)要盡量解耦前后端分離模型只是其中一個(gè)可替換的模塊。我當(dāng)時(shí)給自己定的目標(biāo)是完成一個(gè)不低于80類常見(jiàn)花卉的識(shí)別系統(tǒng)測(cè)試集準(zhǔn)確率90%以上單次推理時(shí)間在500毫秒左右。這個(gè)目標(biāo)放在今天的技術(shù)條件下并不夸張遷移學(xué)習(xí)模型很容易達(dá)到但它能給你一條非常明確的主線所有后續(xù)工作都圍繞它展開。1.2 技術(shù)選型考量為什么是深度學(xué)習(xí)而不是傳統(tǒng)圖像識(shí)別這里多說(shuō)幾句。遇到識(shí)花需求第一反應(yīng)是先用顏色直方圖、花瓣輪廓、紋理特征這些傳統(tǒng)方法。我一開始也試著用OpenCV提取HSV顏色特征加SVM分類器做過(guò)一個(gè)原型結(jié)果很有意思純色背景的玫瑰花照片識(shí)別效果還行一換到真實(shí)場(chǎng)景——背景有草有樹、光線忽明忽暗、花只占畫面三分之一——效果就崩了。原因很簡(jiǎn)單花卉識(shí)別的難點(diǎn)本來(lái)就不在花長(zhǎng)得不一樣而在背景和環(huán)境的干擾太強(qiáng)。傳統(tǒng)方法靠人工設(shè)計(jì)特征本質(zhì)上是人先總結(jié)規(guī)律再讓程序去匹配你能想到的規(guī)則有限照片里的變化是無(wú)限的。深度學(xué)習(xí)把這個(gè)問(wèn)題變成了自動(dòng)找特征卷積神經(jīng)網(wǎng)絡(luò)從大量數(shù)據(jù)中自己學(xué)會(huì)哪些模式是有區(qū)分度的。舉一個(gè)生活中容易理解的例子。你讓一個(gè)小孩子認(rèn)識(shí)蘋果不是說(shuō)紅色圓形就是蘋果而是帶TA見(jiàn)過(guò)各種蘋果紅富士、青蘋果、磕碰過(guò)的、帶葉子的、切開半個(gè)的。見(jiàn)多了之后TA心里自然形成了一個(gè)蘋果的感覺(jué)以后哪怕在昏暗燈光下看到一個(gè)不太紅的蘋果也能認(rèn)出來(lái)。卷積神經(jīng)網(wǎng)絡(luò)做的事情就是把這個(gè)見(jiàn)過(guò)很多次的過(guò)程變成可計(jì)算的數(shù)學(xué)優(yōu)化。當(dāng)然不是說(shuō)傳統(tǒng)方法一無(wú)是處數(shù)據(jù)量很小、場(chǎng)景高度可控的時(shí)候它反而更輕快。但識(shí)花系統(tǒng)的場(chǎng)景天然是開放和復(fù)雜的所以核心識(shí)別模塊選定為深度學(xué)習(xí)圖像分類這是整個(gè)項(xiàng)目最關(guān)鍵的一個(gè)決策。1.3 系統(tǒng)架構(gòu)分層從數(shù)據(jù)集到用戶界面的完整鏈路動(dòng)手寫代碼之前我先把整個(gè)系統(tǒng)分成四個(gè)層次這個(gè)結(jié)構(gòu)也推薦給所有做類似項(xiàng)目的人。數(shù)據(jù)層負(fù)責(zé)圖像數(shù)據(jù)的采集、清洗、增強(qiáng)和劃分。這一層決定模型的天花板后面所有環(huán)節(jié)做的再好數(shù)據(jù)垃圾結(jié)果就是垃圾。模型層負(fù)責(zé)加載預(yù)訓(xùn)練權(quán)重、訓(xùn)練、微調(diào)、驗(yàn)證、導(dǎo)出模型文件。這一層的核心是遷移學(xué)習(xí)不是從頭訓(xùn)練一個(gè)網(wǎng)絡(luò)。服務(wù)層負(fù)責(zé)把訓(xùn)練好的模型包裝成可用的接口。具體來(lái)說(shuō)用推理腳本加載模型權(quán)重對(duì)上傳的圖片做預(yù)處理然后返回類別和置信度。這個(gè)層可以做成Flask接口也可以直接做成命令行工具取決于項(xiàng)目形態(tài)。應(yīng)用層負(fù)責(zé)用戶交互包括前端網(wǎng)頁(yè)上傳圖片、展示識(shí)別結(jié)果、花名、置信度、百科信息等等。我做的是Web頁(yè)面上傳框加結(jié)果展示區(qū)邏輯非常簡(jiǎn)單但對(duì)完整性和演示效果來(lái)說(shuō)很重要。這樣分層以后每一層的職責(zé)單一出了任何問(wèn)題都能快速定位——準(zhǔn)確率不高去看數(shù)據(jù)層和模型層接口報(bào)錯(cuò)去看服務(wù)層頁(yè)面不顯示結(jié)果去看應(yīng)用層。很多初學(xué)者喜歡把所有代碼揉在一個(gè)文件里改一個(gè)地方牽著全身動(dòng)項(xiàng)目規(guī)模小還能忍一旦數(shù)據(jù)量上來(lái)、訓(xùn)練次數(shù)多起來(lái)一定是災(zāi)難。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 數(shù)據(jù)集構(gòu)建一張圖都不能馬虎數(shù)據(jù)集是整個(gè)項(xiàng)目的命脈。我在這上面花的時(shí)間占到了總工期的百分之四十以上訓(xùn)練本身反而快得多。很多人失敗的原因不是模型不夠好而是數(shù)據(jù)集太隨意。第一步是確定類別和數(shù)量。我從中國(guó)常見(jiàn)的觀賞花卉和園林花卉里選了120類每類初始收集200到300張圖片。這個(gè)規(guī)模不是拍腦袋定的類別太少撐不起系統(tǒng)感類別太多數(shù)據(jù)收集成本劇增而且會(huì)引入大量相似屬的混淆問(wèn)題。這個(gè)量級(jí)對(duì)一次認(rèn)真的項(xiàng)目來(lái)說(shuō)正好。第二步是數(shù)據(jù)來(lái)源。公開的花卉數(shù)據(jù)集比如Oxford 102 Flowers質(zhì)量高但只有102類且以歐美常見(jiàn)花卉為主不夠用。我的做法是組合來(lái)源公開數(shù)據(jù)集的子集做底子再補(bǔ)充網(wǎng)絡(luò)圖片和實(shí)拍圖片。補(bǔ)充圖片用腳本批量下載然后花大量時(shí)間人工篩掉模糊、帶水印、主體太小、明顯標(biāo)注錯(cuò)誤的圖。這個(gè)篩選過(guò)程極度枯燥我通常會(huì)寫一個(gè)簡(jiǎn)單的預(yù)處理工具先剔除尺寸過(guò)小、清晰度評(píng)分過(guò)低的圖片再人工二次過(guò)目。第三步是統(tǒng)一格式。所有圖像統(tǒng)一為RGB三通道尺寸在進(jìn)入網(wǎng)絡(luò)前統(tǒng)一縮放到224x224。圖片不要求本體是方的縮放策略是等比縮放后居中裁剪避免直接拉伸導(dǎo)致花朵變形。舉個(gè)例子一張1200x800的橫版照片先縮放成邊長(zhǎng)約256左右的圖再在中心取224x224的方形區(qū)域。這樣做的好處是保留花瓣的盡可能多信息而不是把整張圖壓成容易變形的比例。數(shù)據(jù)增強(qiáng)是另一個(gè)必須做的環(huán)節(jié)。原始樣本沒(méi)有那么多而且網(wǎng)絡(luò)對(duì)翻轉(zhuǎn)、微小的色彩變化天然應(yīng)該具備魯棒性所以訓(xùn)練時(shí)動(dòng)態(tài)做增強(qiáng)隨機(jī)水平翻轉(zhuǎn)、隨機(jī)旋轉(zhuǎn)正負(fù)15度、隨機(jī)亮度對(duì)比度擾動(dòng)、隨機(jī)裁剪一小塊再縮放回去。增強(qiáng)不是把圖片變花哨而是讓模型見(jiàn)過(guò)更多同類但不一樣的照片從而學(xué)到本質(zhì)特征而不是死記硬背。我自己做的一個(gè)小工具函數(shù)用來(lái)統(tǒng)計(jì)每個(gè)類別的圖片數(shù)量。整個(gè)數(shù)據(jù)集不是一次到位的先訓(xùn)練一輪發(fā)現(xiàn)哪些類別準(zhǔn)確率低再去針對(duì)性補(bǔ)數(shù)據(jù)這個(gè)迭代思路在后面排查環(huán)節(jié)還會(huì)展開講。2.2 模型選擇ResNet、MobileNet還是EfficientNet現(xiàn)在做圖像分類基本沒(méi)人從零搭一個(gè)VGG或者ResNet從頭訓(xùn)都是加載預(yù)訓(xùn)練權(quán)重做遷移學(xué)習(xí)。但預(yù)訓(xùn)練模型選哪個(gè)要結(jié)合自己的硬件條件和場(chǎng)景來(lái)定我把自己實(shí)測(cè)對(duì)比過(guò)的三個(gè)候選列出來(lái)。ResNet系列是經(jīng)典選擇。ResNet50是很多教學(xué)項(xiàng)目的主角結(jié)構(gòu)穩(wěn)定、社區(qū)資料多、任何奇怪的報(bào)錯(cuò)都能搜到答案對(duì)初學(xué)者極度友好。但它在CPU上推理偏慢GPU上訓(xùn)練優(yōu)勢(shì)明顯模型文件大概一百多兆部署到服務(wù)器沒(méi)問(wèn)題打包成小程序或App就偏大了。MobileNet系列主打輕量。MobileNetV3或者M(jìn)obileNetV2做成分類器模型文件只有十幾兆到幾十兆CPU推理速度很快適合最終要部署到移動(dòng)端或者低配服務(wù)器的場(chǎng)景。它的準(zhǔn)確率比ResNet略低但在遷移學(xué)習(xí)的加持下差距不大我的實(shí)測(cè)在120類花卉上大概差一到兩個(gè)百分點(diǎn)。EfficientNet系列是效率和精度的平衡點(diǎn)。EfficientNetB0的參數(shù)量比ResNet50小準(zhǔn)確率卻往往更高是性能和資源兼顧的選擇。缺點(diǎn)是某些版本的訓(xùn)練細(xì)節(jié)更敏感比如輸入分辨率和正則化策略需要微調(diào)對(duì)新手不夠友好。我做項(xiàng)目時(shí)選的是EfficientNetB0做主力實(shí)驗(yàn)同時(shí)用MobileNetV3做對(duì)比最后根據(jù)部署目標(biāo)決定用哪一個(gè)。很多同學(xué)喜歡在模型選擇上花大量時(shí)間反復(fù)對(duì)比我的建議是不要陷入選擇困難先選一個(gè)ResNet50從數(shù)據(jù)前處理到訓(xùn)練代碼全部跑通有了完整pipeline之后換模型只是改不到十行代碼的事情。兩個(gè)實(shí)測(cè)數(shù)據(jù)供參考在同樣的120類、每類250張圖片、20個(gè)epoch訓(xùn)練條件下ResNet50在驗(yàn)證集上準(zhǔn)確率大約91.2%EfficientNetB0大約92.8%MobileNetV3大約90.4%。推理時(shí)間在普通GPU上分別是約380ms、約240ms、約120ms含預(yù)處理。數(shù)字因平臺(tái)而異但相對(duì)關(guān)系是穩(wěn)定的。2.3 訓(xùn)練策略遷移學(xué)習(xí)的正確打開方式遷移學(xué)習(xí)的核心思路不是從零學(xué)而是站在巨人肩膀上。預(yù)訓(xùn)練模型已經(jīng)在ImageNet這種千萬(wàn)級(jí)圖片上學(xué)過(guò)通用的視覺(jué)特征——邊緣、紋理、形狀、物體部件——我們要做的是讓它適應(yīng)花卉這個(gè)小領(lǐng)域。關(guān)鍵操作是分層訓(xùn)練。預(yù)訓(xùn)練模型一般分成特征提取部分前面的卷積層和分類頭最后的全連接層。標(biāo)準(zhǔn)做法是先把分類頭替換成自己定制的新結(jié)構(gòu)——因?yàn)镮mageNet是1000類我們只有120類——然后凍結(jié)所有特征提取層的權(quán)重只訓(xùn)練新的分類頭。這個(gè)階段的訓(xùn)練很快幾個(gè)epoch就能達(dá)到一個(gè)不算差的起點(diǎn)。之后再視情況解凍部分高層卷積層用更小的學(xué)習(xí)率微調(diào)。這樣安排的原因是底層特征非常通用不需要大改高層特征更接近原任務(wù)的語(yǔ)義層面需要更多適應(yīng)。具體參數(shù)我采用的是優(yōu)化器Adam初始學(xué)習(xí)率凍結(jié)階段設(shè)置3e-4微調(diào)階段降到1e-4或者5e-5batch size選32顯存不夠就16效果差距不大訓(xùn)練總輪數(shù)20到30個(gè)epoch學(xué)習(xí)率用余弦退火或分段下降都可以。早停法一定要開監(jiān)控驗(yàn)證集損失連續(xù)若干個(gè)epoch不降就停止避免過(guò)擬合。這個(gè)方法幫我省了大量無(wú)意義的等待時(shí)間。提示訓(xùn)練過(guò)程中不要只看訓(xùn)練集準(zhǔn)確率。訓(xùn)練集準(zhǔn)確率接近100%而驗(yàn)證集只有85%這就是典型的過(guò)擬合信號(hào)。真正的監(jiān)控指標(biāo)是驗(yàn)證集loss和精度。損失函數(shù)就是普通的交叉熵類別多、樣本還算均衡的情況下沒(méi)必要上focal loss之類的高級(jí)貨。但如果你的類別樣本量差異很大比如某類有500張、某類只有50張可以考慮在交叉熵里加類別權(quán)重把樣本少的類別權(quán)重調(diào)高一點(diǎn)讓模型更重視它們。這個(gè)重視不是玄學(xué)是數(shù)學(xué)上通過(guò)調(diào)節(jié)梯度貢獻(xiàn)實(shí)現(xiàn)的。3. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 環(huán)境準(zhǔn)備清單與工具鏈選擇我用的環(huán)境是Ubuntu 22.04Python 3.10PyTorch 2.xGPU是一張中端顯卡顯存12G。如果你的電腦沒(méi)有NVIDIA顯卡純CPU也能完成訓(xùn)練只是時(shí)間會(huì)多幾倍建議把圖片尺寸調(diào)小、模型選MobileNet、epoch減少先跑通流程再考慮完整訓(xùn)練。依賴庫(kù)非常標(biāo)準(zhǔn)pip install torch torchvision opencv-python pillow numpy flask如果訓(xùn)練需要自動(dòng)監(jiān)控和日志記錄再安裝tensorboard或者wandb但最基礎(chǔ)的訓(xùn)練代碼用print和csv日志就足夠了。不要一上來(lái)就堆一堆工具工程里最樸素的方案往往最穩(wěn)定。3.2 數(shù)據(jù)處理流程可以這樣寫核心是把圖片從文件夾讀取、做增強(qiáng)、變成張量這三個(gè)動(dòng)作封裝好。文件夾結(jié)構(gòu)是datasets/train/類名/圖片.jpg驗(yàn)證集類似這個(gè)規(guī)范讓增加新類別變得很簡(jiǎn)單——新建文件夾放圖片就行。用PyTorch自帶的ImageFolder讀取數(shù)據(jù)搭配torchvision的transforms做預(yù)處理和增強(qiáng)代碼大概長(zhǎng)這樣from torchvision import transforms from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder train_transforms transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset ImageFolder(datasets/train, transformtrain_transforms) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4)Normalize那一行的mean和std是ImageNet統(tǒng)計(jì)出來(lái)的固定值不要自己算。這個(gè)標(biāo)準(zhǔn)化操作和預(yù)訓(xùn)練權(quán)重是對(duì)應(yīng)的改掉會(huì)導(dǎo)致模型效果明顯變差。3.3 模型訓(xùn)練關(guān)鍵代碼與參數(shù)調(diào)優(yōu)日志訓(xùn)練主循環(huán)其實(shí)很樸素但有一個(gè)細(xì)節(jié)值得單獨(dú)說(shuō)明加載預(yù)訓(xùn)練權(quán)重的正確姿勢(shì)。以EfficientNetB0為例不要手動(dòng)下載權(quán)重文件換成自己的隨機(jī)初始化直接加載官方預(yù)訓(xùn)練權(quán)重到特征層然后覆蓋掉分類頭。import torch import torch.nn as nn import torch.optim as optim from torchvision import models model models.efficientnet_b0(weightsmodels.EfficientNetB0_Weights.IMAGENET1K_V1) num_features model.classifier[1].in_features model.classifier nn.Sequential( nn.Dropout(p0.2), nn.Linear(num_features, num_classes) ) # 凍結(jié)特征層 for param in model.features.parameters(): param.requires_grad False optimizer optim.Adam(model.classifier.parameters(), lr3e-4) criterion nn.CrossEntropyLoss()訓(xùn)練腳本我習(xí)慣寫成函數(shù)式結(jié)構(gòu)train_one_epoch(epoch)、validate(epoch)、save_checkpoint。每個(gè)epoch結(jié)束都算一下驗(yàn)證集準(zhǔn)確率記錄到一個(gè)csv里方便后續(xù)畫曲線和分析。下面是我某一個(gè)關(guān)鍵版本實(shí)驗(yàn)的日志節(jié)選可以直接感受訓(xùn)練過(guò)程的變化Epoch TrainLoss TrainAcc ValLoss ValAcc 1 1.8321 56.34% 1.4210 72.18% 5 0.7214 78.92% 0.5823 83.45% 10 0.3822 89.07% 0.4021 89.12% 15 0.2135 94.83% 0.3038 91.07% 20 0.1420 97.21% 0.2786 91.86% 25 0.0983 98.54% 0.2654 92.24%觀察這個(gè)日志你會(huì)發(fā)現(xiàn)一個(gè)非常典型的現(xiàn)象訓(xùn)練集準(zhǔn)確率一路上漲但驗(yàn)證集準(zhǔn)確率在某個(gè)epoch之后幾乎持平甚至下降。這就是模型開始過(guò)擬合的信號(hào)。我一般在第15到20個(gè)epoch之間保存效果最好的那一版而不是最后一次迭代的權(quán)重。解凍微調(diào)的代碼也順便給出for param in model.features.parameters(): param.requires_grad True optimizer optim.Adam(model.parameters(), lr1e-5)這個(gè)階段只跑三四個(gè)epoch學(xué)習(xí)率要小防止把預(yù)訓(xùn)練學(xué)到的好的底層特征破壞掉。3.4 服務(wù)端部署與接口實(shí)現(xiàn)模型訓(xùn)練完之后部署環(huán)節(jié)需要做一個(gè)取舍不可能在服務(wù)端加載整個(gè)訓(xùn)練框架跑推理所以要把模型導(dǎo)出成輕量的推理格式或直接在Flask里加載權(quán)重文件但確保只在啟動(dòng)時(shí)加載一次不要每次請(qǐng)求都重新導(dǎo)入模型。我實(shí)現(xiàn)的是FastAPI接口邏輯是接收上傳的圖片文件做和訓(xùn)練時(shí)一致的預(yù)處理然后用模型推理得到類別索引和置信度。from fastapi import FastAPI, UploadFile from PIL import Image import torchvision.transforms as transforms app FastAPI() model load_model(best_model.pth) class_names load_class_names(class_names.txt) app.post(/predict) async def predict(file: UploadFile): image Image.open(file.file).convert(RGB) tensor val_transforms(image).unsqueeze(0) with torch.no_grad(): outputs model(tensor) probs torch.softmax(outputs, dim1) conf, idx torch.max(probs, dim1) result { flower: class_names[idx.item()], confidence: round(conf.item(), 4) } return result注意convert(RGB)這一步。很多用戶上傳的手機(jī)照片是RGBA或者帶EXIF方向信息的不轉(zhuǎn)RGB會(huì)導(dǎo)致通道數(shù)對(duì)不上不做方向校正會(huì)導(dǎo)致圖片旋轉(zhuǎn)。這些細(xì)節(jié)在實(shí)際測(cè)試中一定會(huì)遇到提前處理比等報(bào)錯(cuò)再解決舒服得多。前端頁(yè)面我用了最簡(jiǎn)單的HTML加原生JavaScript一個(gè)文件上傳框、一個(gè)預(yù)覽區(qū)、一個(gè)結(jié)果展示卡。這個(gè)形態(tài)適合課設(shè)演示也適合快速驗(yàn)證系統(tǒng)效果。不一定要用Vue、React之類的框架功能簡(jiǎn)單時(shí)越少依賴越穩(wěn)定。3.5 萬(wàn)字設(shè)計(jì)報(bào)告怎么寫才有干貨如果你做這個(gè)項(xiàng)目是為了畢業(yè)設(shè)計(jì)那報(bào)告結(jié)構(gòu)和寫代碼同樣重要。我見(jiàn)過(guò)太多人代碼寫得很溜但報(bào)告全是復(fù)制粘貼的廢話最后答辯被問(wèn)住。合理的報(bào)告結(jié)構(gòu)大致是緒論背景與意義、國(guó)內(nèi)外研究現(xiàn)狀、主要工作、相關(guān)技術(shù)介紹深度學(xué)習(xí)基礎(chǔ)、卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)、遷移學(xué)習(xí)原理、常用模型對(duì)比、系統(tǒng)需求分析功能需求、性能需求、可行性分析、系統(tǒng)設(shè)計(jì)總體架構(gòu)、數(shù)據(jù)集構(gòu)建方案、模型設(shè)計(jì)、功能模塊設(shè)計(jì)、系統(tǒng)實(shí)現(xiàn)環(huán)境搭建、數(shù)據(jù)預(yù)處理實(shí)現(xiàn)、模型訓(xùn)練實(shí)現(xiàn)、接口與界面實(shí)現(xiàn)、系統(tǒng)測(cè)試測(cè)試環(huán)境、功能測(cè)試用例、性能測(cè)試結(jié)果、準(zhǔn)確率對(duì)比分析、總結(jié)與展望。這里最容易被老師挑毛病的是國(guó)內(nèi)外研究現(xiàn)狀和測(cè)試分析。研究現(xiàn)狀別只寫深度學(xué)習(xí)發(fā)展很快這種話要把具體方法分類列出來(lái)基于傳統(tǒng)特征的方法、基于通用CNN的方法、基于注意力機(jī)制的方法等等。測(cè)試分析必須有數(shù)據(jù)支撐比如不同模型的準(zhǔn)確率對(duì)比表、不同訓(xùn)練策略的對(duì)比實(shí)驗(yàn)、不同光照條件下的識(shí)別效果分析。這些都是你真實(shí)做過(guò)的東西寫出來(lái)的自然有說(shuō)服力。我的做法是在項(xiàng)目開發(fā)過(guò)程中隨手記錄實(shí)驗(yàn)表格和結(jié)果截圖最后寫報(bào)告時(shí)把這些素材組裝起來(lái)比自己憑空編造效率高得多內(nèi)容也更扎實(shí)。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 訓(xùn)練準(zhǔn)確率上不去的定位思路遇到準(zhǔn)確率低先不要懷疑模型結(jié)構(gòu)絕大多數(shù)問(wèn)題出在數(shù)據(jù)處理和訓(xùn)練配置上。我整理了一個(gè)定位順序先看訓(xùn)練集準(zhǔn)確率再對(duì)比驗(yàn)證集準(zhǔn)確率。如果兩者都低說(shuō)明模型容量不夠或者學(xué)習(xí)率有問(wèn)題。試試更大的模型、更長(zhǎng)時(shí)間訓(xùn)練、更合適的學(xué)習(xí)率。如果訓(xùn)練集高、驗(yàn)證集低說(shuō)明過(guò)擬合或者數(shù)據(jù)分布有問(wèn)題。優(yōu)先檢查數(shù)據(jù)集是否有類別重復(fù)、是否有的類別圖片和別的類別高度相似、是否有大量非花卉圖片混在里面。這幾個(gè)問(wèn)題我實(shí)際都踩過(guò)某次訓(xùn)練驗(yàn)證集卡在85%上不去排查后發(fā)現(xiàn)兩個(gè)類別的訓(xùn)練圖片里混了不少標(biāo)注錯(cuò)誤的圖一張張篩選掉之后準(zhǔn)確率直接到了90%以上。還有一種隱蔽的情況是類別不平衡。如果某個(gè)類別只有30張圖片而其他類別有300張模型很容易對(duì)前者欠擬合。解決辦法不只是加數(shù)據(jù)還可以在采樣時(shí)對(duì)樣本少的類別做過(guò)采樣或者在損失函數(shù)里加類別權(quán)重。4.2 過(guò)擬合的判斷標(biāo)準(zhǔn)與處理路徑過(guò)擬合最直觀的表現(xiàn)就是訓(xùn)練集準(zhǔn)確率和驗(yàn)證集準(zhǔn)確率之間的gap越來(lái)越大。處理辦法按優(yōu)先級(jí)排列增加數(shù)據(jù)增強(qiáng)的強(qiáng)度是最溫和的手段。把隨機(jī)旋轉(zhuǎn)從15度擴(kuò)大到30度增加隨機(jī)擦除人為制造更多變化。其次是加正則化。Dropout的默認(rèn)值一般是0.2可以調(diào)到0.3到0.5或者加權(quán)重衰減。遷移學(xué)習(xí)場(chǎng)景下還有一個(gè)非常有效的做法少解凍一些層或者降低微調(diào)階段的學(xué)習(xí)率防止模型在有限數(shù)據(jù)上劇烈變化。我在實(shí)際項(xiàng)目中遇到過(guò)這樣一個(gè)情況微調(diào)階段解凍了模型全部層學(xué)習(xí)率用了2e-4結(jié)果驗(yàn)證集準(zhǔn)確率出現(xiàn)明顯震蕩某個(gè)epoch從91%掉到87%。換成只解凍最后兩個(gè)stage、學(xué)習(xí)率降到5e-5之后震蕩消失最終穩(wěn)定在92%左右。這說(shuō)明在數(shù)據(jù)量不夠大的時(shí)候解凍層數(shù)不是越多越好。4.3 推理階段預(yù)測(cè)速度慢的常見(jiàn)原因如果部署之后發(fā)現(xiàn)識(shí)別要兩三秒才能出結(jié)果先別急著換輕量模型。按順序排查第一模型是否每次請(qǐng)求都重新加載。很多人會(huì)把模型加載代碼寫在接口函數(shù)內(nèi)部導(dǎo)致每識(shí)別一張圖都重新讀一遍權(quán)重文件這是最典型的低級(jí)錯(cuò)誤。應(yīng)該把模型加載放到服務(wù)啟動(dòng)階段全局只加載一次。第二是否用了GPU但推理時(shí)沒(méi)有切到cuda。第三輸入圖片是否被resize得過(guò)大。有些代碼寫的是resize到800x800而不是224x224計(jì)算量成倍上升。第四批量推理不等同于單張推理部署時(shí)核對(duì)一下代碼路徑有沒(méi)有走batch維度。我的實(shí)際經(jīng)驗(yàn)是把模型放對(duì)設(shè)備、輸入尺寸固定為224后EfficientNetB0在普通CPU上推理也能做到500毫秒左右這個(gè)速度已經(jīng)滿足一般交互需求了。4.4 項(xiàng)目中容易踩的技術(shù)坑速查表做一個(gè)表格把典型問(wèn)題列出來(lái)方便你對(duì)照排查問(wèn)題現(xiàn)象可能原因解決辦法訓(xùn)練loss不下降學(xué)習(xí)率過(guò)大或過(guò)小、數(shù)據(jù)格式錯(cuò)誤調(diào)學(xué)習(xí)率檢查標(biāo)簽和圖片是否對(duì)應(yīng)驗(yàn)證準(zhǔn)確率忽高忽低學(xué)習(xí)率過(guò)大、batch size過(guò)小降低學(xué)習(xí)率、適當(dāng)增加batch某幾個(gè)類別永遠(yuǎn)分不清數(shù)據(jù)標(biāo)注混亂、類別視覺(jué)上過(guò)于接近清洗數(shù)據(jù)、查找相似類的典型混淆樣本、補(bǔ)數(shù)據(jù)推理結(jié)果和訓(xùn)練效果差距大預(yù)處理不一致確保推理時(shí)的resize、normalize和訓(xùn)練完全一致內(nèi)存炸了圖片一次性全讀入內(nèi)存用DataLoader流式讀取調(diào)小batch上傳的圖片識(shí)別報(bào)通道錯(cuò)誤圖片是RGBA格式統(tǒng)一convert(RGB)保存模型后發(fā)現(xiàn)類名對(duì)不上類別排序錯(cuò)亂訓(xùn)練時(shí)保存一份class_to_idx的映射文件這些坑都很低級(jí)但每一個(gè)都真實(shí)發(fā)生在很多項(xiàng)目里。我自己最快的一次排查只花了五分鐘就發(fā)現(xiàn)問(wèn)題原因是推理時(shí)忘記做標(biāo)準(zhǔn)化像素范圍還停留在0到255和訓(xùn)練時(shí)的0到1輸入完全不在一個(gè)量級(jí)。這類問(wèn)題在別人眼里就是模型不準(zhǔn)實(shí)際就是一行代碼的事。5. 項(xiàng)目成果復(fù)盤與經(jīng)驗(yàn)擴(kuò)展5.1 從單一識(shí)別到完整系統(tǒng)的價(jià)值閉環(huán)很多做課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的同學(xué)做完訓(xùn)練模型就以為項(xiàng)目結(jié)束了這是非??上У?。反過(guò)來(lái)想一個(gè)只有訓(xùn)練腳本和幾個(gè)準(zhǔn)確率數(shù)字的項(xiàng)目別人怎么看都覺(jué)得這就是交作業(yè)一旦你把識(shí)別接口、前后端頁(yè)面、演示視頻串起來(lái)整個(gè)項(xiàng)目就活了。用戶能上手用、能上傳照片、能看到結(jié)果這個(gè)系統(tǒng)的價(jià)值立即展現(xiàn)出來(lái)了。以我這個(gè)項(xiàng)目為例最終交付的不只是模型權(quán)重文件還包括完整的訓(xùn)練代碼、推理服務(wù)、前端頁(yè)面、數(shù)據(jù)集說(shuō)明文檔、實(shí)驗(yàn)記錄表、項(xiàng)目報(bào)告和答辯PPT。這些材料合在一起就構(gòu)成了一個(gè)產(chǎn)品。你去面試或者在答辯現(xiàn)場(chǎng)把自己的系統(tǒng)從頭到尾操作一遍比在簡(jiǎn)歷上寫一百個(gè)技術(shù)名詞有用得多。5.2 后續(xù)擴(kuò)展從靜態(tài)識(shí)別到更大的產(chǎn)品想象空間系統(tǒng)在基礎(chǔ)功能跑通之后擴(kuò)展方向非常多。最直接的是把識(shí)別從單花拓展到多花檢測(cè)——一張照片里有多種花先用目標(biāo)檢測(cè)模型框出來(lái)再逐一分類。技術(shù)上可以用Faster R-CNN或者YOLO系列在原有數(shù)據(jù)集基礎(chǔ)上增加標(biāo)注框。這個(gè)擴(kuò)展讓系統(tǒng)從單主體識(shí)別升級(jí)到場(chǎng)景理解適用面一下大了很多。另一個(gè)有價(jià)值的擴(kuò)展方向是增加交互反饋機(jī)制。用戶可以標(biāo)注識(shí)別結(jié)果正確或不正確正確結(jié)果可以進(jìn)入數(shù)據(jù)池增強(qiáng)模型不正確的結(jié)果可以觸發(fā)人工復(fù)核。這就是一個(gè)非常輕量的人機(jī)協(xié)同閉環(huán)會(huì)讓模型的實(shí)用價(jià)值隨時(shí)間增長(zhǎng)。不要小看這個(gè)想法很多商業(yè)識(shí)花產(chǎn)品就是這樣積累數(shù)據(jù)、提升準(zhǔn)確率的。5.3 經(jīng)驗(yàn)沉淀我給后來(lái)者的三條操作性建議第一條建議是先跑通極其小的版本。不要一上來(lái)就收集120類圖片、訓(xùn)練20個(gè)epoch。先拿10類、每類50張圖、5個(gè)epoch把全流程跑通。這個(gè)最小可用閉環(huán)能讓你快速暴露代碼和工具鏈里的問(wèn)題等流程順暢了再擴(kuò)數(shù)據(jù)和加算力。我見(jiàn)過(guò)太多人連環(huán)境都沒(méi)裝好就直接沖大項(xiàng)目最后卡在各種莫名其妙的報(bào)錯(cuò)上心態(tài)直接崩掉。第二條建議是從第一個(gè)epoch開始就寫實(shí)驗(yàn)日志。哪怕只是簡(jiǎn)單記錄每個(gè)epoch的訓(xùn)練loss、驗(yàn)證loss、準(zhǔn)確率它對(duì)后續(xù)改進(jìn)的價(jià)值也是巨大的。沒(méi)有日志你改了一個(gè)參數(shù)都不知道效果是變好還是變壞整個(gè)調(diào)優(yōu)過(guò)程就成了盲人摸象。第三條建議是每條代碼路徑都保留可復(fù)現(xiàn)性。數(shù)據(jù)集劃分、預(yù)處理參數(shù)、模型結(jié)構(gòu)、訓(xùn)練超參全部記錄在配置文件和文檔里。這個(gè)習(xí)慣在寫報(bào)告時(shí)幫你節(jié)省大量時(shí)間也會(huì)讓項(xiàng)目在未來(lái)被重新打開時(shí)仍然可以一鍵復(fù)現(xiàn)。很多二手代碼打開就報(bào)錯(cuò)就是因?yàn)榄h(huán)境依賴和參數(shù)設(shè)置完全沒(méi)有記錄。6. 實(shí)戰(zhàn)復(fù)盤那些文檔里不會(huì)寫的東西文章寫到這里主體內(nèi)容基本完整了。最后聊一點(diǎn)個(gè)人體會(huì)。做這個(gè)智慧識(shí)花系統(tǒng)技術(shù)上最難的從來(lái)不是調(diào)用模型、寫接口而是你有沒(méi)有耐心把數(shù)據(jù)集一點(diǎn)點(diǎn)洗干凈、有沒(méi)有習(xí)慣每跑一個(gè)實(shí)驗(yàn)都記錄數(shù)據(jù)、有沒(méi)有意識(shí)在訓(xùn)練代碼之外補(bǔ)全工程化模塊。我最后用的模型其實(shí)非常簡(jiǎn)單就是EfficientNetB0加遷移學(xué)習(xí)但在120類、72000張圖片這個(gè)體量上它達(dá)成了92%以上的驗(yàn)證準(zhǔn)確率上線之后收到的反饋也不錯(cuò)。如果你也想做一個(gè)同類項(xiàng)目或者你正在寫相關(guān)報(bào)告我的建議是別把精力浪費(fèi)在選最好的模型上而是死磕兩件事干凈的數(shù)據(jù)集、規(guī)范的工程流程。這兩件事做扎實(shí)結(jié)果一定不會(huì)差。最后再分享一個(gè)我一直在用的小技巧每次訓(xùn)練結(jié)束后把訓(xùn)練日志、模型評(píng)估、一張結(jié)果混淆矩陣保存成一個(gè)以日期命名的快照目錄。時(shí)間久了你會(huì)發(fā)現(xiàn)這種簡(jiǎn)單的存檔習(xí)慣能讓你無(wú)比清晰地看到整個(gè)項(xiàng)目是怎樣一步步變好的做報(bào)告、復(fù)盤、定位問(wèn)題都極其順手。