物疲勞識(shí)別:從圖像分類到深度學(xué)習(xí)全流程實(shí)戰(zhàn))
做畢設(shè)選題的時(shí)候很多同學(xué)一聽到“動(dòng)物疲勞識(shí)別”第一反應(yīng)是“這東西怎么做”第二反應(yīng)是“是不是太偏了”。實(shí)際上這個(gè)題目放到計(jì)算機(jī)視覺的框架里就是一個(gè)典型的圖像二分類問題給一張動(dòng)物圖像判斷它處于疲勞狀態(tài)還是正常狀態(tài)。沒有目標(biāo)檢測(cè)沒有語義分割沒有姿態(tài)估計(jì)門檻非常友好但它又足夠完整能讓你把數(shù)據(jù)采集、標(biāo)注、模型訓(xùn)練、評(píng)估、部署這一整套深度學(xué)習(xí)流程全部走一遍。這篇文章就以我實(shí)際帶過的一個(gè)模擬項(xiàng)目X為例把基于CNN卷積網(wǎng)絡(luò)的動(dòng)物疲勞識(shí)別從零到落地講清楚適合正在準(zhǔn)備畢業(yè)設(shè)計(jì)、打算入門圖像分類的讀者直接參考。我先把話放在前面這個(gè)項(xiàng)目的核心價(jià)值不在于“識(shí)別準(zhǔn)確率能有多高”而在于你能不能把每一個(gè)環(huán)節(jié)都處理好。數(shù)據(jù)定義是否合理、標(biāo)注是否一致、訓(xùn)練過程是否可復(fù)現(xiàn)、評(píng)估指標(biāo)是否選對(duì)了這些才是答辯和高分的關(guān)鍵。下面我按整個(gè)項(xiàng)目推進(jìn)的時(shí)間線來拆解每一部分都會(huì)給出我實(shí)際操作中的具體參數(shù)、踩過的坑和修正經(jīng)驗(yàn)。1. 選題分析與整體技術(shù)路線1.1 這個(gè)題目為什么會(huì)成為一個(gè)好的畢設(shè)選題很多同學(xué)選畢設(shè)題目時(shí)會(huì)陷入兩個(gè)極端要么選一個(gè)非常宏大但根本做不完的方向比如“基于深度學(xué)習(xí)的智能養(yǎng)殖系統(tǒng)”聽起來很唬人但涉及檢測(cè)、跟蹤、識(shí)別、告警好幾塊一個(gè)人短期根本搞不定要么選一個(gè)過于簡(jiǎn)單的東西比如用現(xiàn)成的模型跑一下公開數(shù)據(jù)集寫個(gè)報(bào)告就完事答辯時(shí)很容易被問住?!皠?dòng)物疲勞識(shí)別”恰好卡在中間位置。從技術(shù)難度看它不需要你做復(fù)雜的目標(biāo)定位只要圖像里動(dòng)物主體相對(duì)明確CNN就能直接端到端學(xué)習(xí)疲勞特征。從算法深度看它涉及到圖像分類任務(wù)的典型難點(diǎn)——類間差異小、類內(nèi)差異大、樣本不均衡這些都是可以寫進(jìn)論文的實(shí)質(zhì)內(nèi)容。從應(yīng)用價(jià)值看疲勞狀態(tài)監(jiān)測(cè)在畜牧業(yè)、寵物健康監(jiān)護(hù)、實(shí)驗(yàn)動(dòng)物狀態(tài)評(píng)估等領(lǐng)域都有真實(shí)需求不是憑空捏造的場(chǎng)景。我當(dāng)時(shí)給A同學(xué)建議這個(gè)題目的時(shí)候特別強(qiáng)調(diào)了一點(diǎn)不要試圖一步到位做一個(gè)“全自動(dòng)畜牧疲勞監(jiān)測(cè)系統(tǒng)”那是產(chǎn)品級(jí)目標(biāo)。畢設(shè)的核心是把“基于CNN的動(dòng)物疲勞識(shí)別”這個(gè)算法鏈路做扎實(shí)最后能給出一個(gè)可復(fù)現(xiàn)的模型、一組可靠的評(píng)估數(shù)據(jù)、一個(gè)簡(jiǎn)單的推理調(diào)用方式就非常完整了。這個(gè)定位很重要因?yàn)楹芏嗳说漠呍O(shè)最后爛尾不是技術(shù)能力不夠而是目標(biāo)定得太大時(shí)間被各種邊角料吞掉了。1.2 疲勞狀態(tài)怎么定義數(shù)據(jù)從哪來這個(gè)題目的第一個(gè)坑就是對(duì)“疲勞”的定義。疲勞不是一只動(dòng)物站在那里寫著“我很累”它是一種需要人為約定的視覺狀態(tài)。如果不把定義先想清楚后面所有的標(biāo)注都會(huì)亂套。在實(shí)際操作里我們采用的是“行為學(xué)特征 影像特征”結(jié)合的定義方式。針對(duì)常見的哺乳動(dòng)物以某實(shí)驗(yàn)農(nóng)場(chǎng)的一批動(dòng)物為樣本我們約定以下可辨識(shí)特征作為疲勞狀態(tài)的判斷依據(jù)眼睛閉合程度疲勞狀態(tài)下眼睛閉合面積明顯超過正常狀態(tài)或者出現(xiàn)持續(xù)性半閉耳朵姿態(tài)健康狀態(tài)下耳朵經(jīng)常轉(zhuǎn)動(dòng)、豎起疲勞狀態(tài)下耳朵低垂活動(dòng)頻率降低頭部角度疲勞的動(dòng)物頭部經(jīng)常低垂少見主動(dòng)環(huán)顧四周的動(dòng)作反應(yīng)速度這個(gè)特征在單張靜態(tài)圖像里無法直接體現(xiàn)所以我們只作為輔助條件不作為主要標(biāo)注依據(jù)。你可以看到這個(gè)定義里面“眼睛閉合程度”“耳朵姿態(tài)”“頭部角度”都是可以在單張圖像上觀察到的這就保證了人工標(biāo)注的可操作性。疲勞狀態(tài)是二分類標(biāo)簽如果后續(xù)你想做成三分類正常、輕度疲勞、重度疲勞可以在此基礎(chǔ)之上擴(kuò)展但畢設(shè)階段建議先做二分類。數(shù)據(jù)來源方面我們用了兩條腿走路一部分來自公開的動(dòng)物行為圖像集另一部分是我們自己在模擬場(chǎng)景下拍攝的。公開數(shù)據(jù)的好處是量大、覆蓋場(chǎng)景廣壞處是標(biāo)注標(biāo)準(zhǔn)不一定和我們的定義一致自采數(shù)據(jù)的好處是標(biāo)注可控壞處是需要花時(shí)間和精力。對(duì)于畢設(shè)來說我建議公開數(shù)據(jù)占比大概七成、自采數(shù)據(jù)三成這樣既能保證數(shù)據(jù)量又能保證評(píng)價(jià)時(shí)有自己可控的驗(yàn)證集。需要注意的是公開數(shù)據(jù)里如果有非常不清晰的圖像寧可不要也不要讓它污染訓(xùn)練集。1.3 整體技術(shù)路線整個(gè)項(xiàng)目的技術(shù)路線可以歸納為五步數(shù)據(jù)采集與清洗、疲勞標(biāo)簽標(biāo)注、圖像預(yù)處理與增強(qiáng)、CNN模型訓(xùn)練與調(diào)優(yōu)、模型評(píng)估與推理封裝。不要跳過任何一步尤其是數(shù)據(jù)清洗和標(biāo)注一致性檢查很多人為了趕進(jìn)度在這一塊偷工減料后期訓(xùn)練時(shí)各種奇怪問題都會(huì)冒出來。模型方面我們采用的是經(jīng)典的卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)。從零搭建了一個(gè)小規(guī)模的CNN同時(shí)也做了基于預(yù)訓(xùn)練殘差網(wǎng)絡(luò)的遷移學(xué)習(xí)實(shí)驗(yàn)最后對(duì)比發(fā)現(xiàn)兩種方案在不同數(shù)據(jù)規(guī)模下的表現(xiàn)差異。對(duì)于有GPU環(huán)境的同學(xué)我建議兩條路線都跑一下答辯時(shí)這是一個(gè)很好的工作量體現(xiàn)如果設(shè)備限制比較大可以從零搭建的小網(wǎng)絡(luò)開始效果也完全夠用。我把整個(gè)流程的時(shí)間分配建議寫在下面表格里你可以根據(jù)自己的周期調(diào)整階段時(shí)間占比說明數(shù)據(jù)采集與標(biāo)注30%決定模型上限最容易被低估預(yù)處理與增強(qiáng)15%清洗、統(tǒng)一尺寸、增強(qiáng)策略模型搭建與訓(xùn)練30%結(jié)構(gòu)設(shè)計(jì)和調(diào)參迭代評(píng)估與分析15%指標(biāo)、混淆矩陣、誤判案例模塊封裝與文檔10%推理腳本、README、答辯材料這個(gè)時(shí)間分配不是我隨便寫的。我見過太多同學(xué)在模型訓(xùn)練上死磕卻忽略了數(shù)據(jù)標(biāo)注的質(zhì)量最后訓(xùn)練出來的模型在測(cè)試集上準(zhǔn)確率很高一拿到自己拍的照片上就崩。原因往往不是模型不行而是訓(xùn)練數(shù)據(jù)和真實(shí)場(chǎng)景差異太大或者標(biāo)注標(biāo)準(zhǔn)不統(tǒng)一導(dǎo)致模型學(xué)到的特征混亂。2. 數(shù)據(jù)集構(gòu)建與預(yù)處理2.1 數(shù)據(jù)采集的實(shí)操細(xì)節(jié)數(shù)據(jù)采集聽起來簡(jiǎn)單不就是拍照片嗎但在實(shí)際操作中有幾個(gè)細(xì)節(jié)直接決定了數(shù)據(jù)的可用性。首先是拍攝角度的一致性。在自采數(shù)據(jù)時(shí)我強(qiáng)烈建議固定攝像頭的安裝高度和角度盡量避免出現(xiàn)俯拍、仰拍混雜的情況。CNN雖然有一定的尺度和平移魯棒性但角度差異過大時(shí)模型很容易學(xué)到“角度特征”而不是“疲勞特征”。我們?cè)谀M場(chǎng)景中把攝像頭固定在側(cè)上方約45度角的位置保證動(dòng)物主體在畫面中央且完整。其次是光線條件。疲勞狀態(tài)判斷非常依賴眼睛和耳朵的細(xì)節(jié)光線過暗時(shí)這些特征會(huì)被噪聲淹沒。自采數(shù)據(jù)的時(shí)候盡量在自然光充足的條件下拍攝不要使用會(huì)導(dǎo)致偏色的彩色光源。如果你之后要對(duì)公開數(shù)據(jù)進(jìn)行清洗優(yōu)先選擇那些主體清晰、光照均勻的圖像模糊的、嚴(yán)重過曝的、主體被遮擋的圖像直接舍棄。第三是樣本的去重問題。很多人從視頻里抽幀如果直接連續(xù)抽幀相鄰幀之間高度相似這些近重復(fù)樣本會(huì)被訓(xùn)練集和驗(yàn)證集同時(shí)包含導(dǎo)致驗(yàn)證準(zhǔn)確率虛高。我們當(dāng)時(shí)抽幀策略是每隔若干幀抽一幀并且抽完以后計(jì)算相鄰幀的像素相似度相似度超過閾值的保留一張其余的丟棄。這個(gè)操作雖然簡(jiǎn)單但能有效避免數(shù)據(jù)泄漏在答辯時(shí)也是可以講的細(xì)節(jié)。最后要檢查的是類別平衡性。疲勞狀態(tài)在自然場(chǎng)景里本來就是少數(shù)情況所以采集到的正常樣本往往比疲勞樣本多很多。我們的初始數(shù)據(jù)集里正常和疲勞的比例大概是3.5比1這個(gè)比例如果不處理模型會(huì)傾向于把所有樣本都預(yù)測(cè)為正常因?yàn)檫@樣準(zhǔn)確率也能到70%以上。后面我會(huì)講具體的處理辦法但至少你要先心里有數(shù)你的數(shù)據(jù)不平衡程度有多嚴(yán)重各是多少?gòu)垐D。2.2 標(biāo)注標(biāo)準(zhǔn)的制定與一致性檢查標(biāo)注是項(xiàng)目中最容易被輕視的環(huán)節(jié)也是影響最終結(jié)果最大的環(huán)節(jié)。一張圖像到底算疲勞還是正常不同人看可能結(jié)果不同所以必須要把標(biāo)準(zhǔn)提前固化下來。我們當(dāng)時(shí)做了一套標(biāo)注規(guī)范文檔里面寫明了各種邊界情況的判定規(guī)則。比如眼睛半閉但耳朵豎直怎么算答案是只要眼睛閉合面積超過整個(gè)眼球面積的一半就傾向判定為疲勞耳朵姿態(tài)作為輔助修正。頭部低垂但眼睛完全睜開怎么算答案是以眼睛狀態(tài)為主判定為正常。這套規(guī)則看起來非常死板但它保證了不同標(biāo)注者之間的一致性。實(shí)際操作中我們讓三個(gè)人獨(dú)立對(duì)同一批圖像進(jìn)行標(biāo)注最后取多數(shù)票作為最終標(biāo)簽。三個(gè)人標(biāo)簽不一致的圖像單獨(dú)挑出來討論討論后仍無法達(dá)成一致的直接棄用。你可能會(huì)覺得三個(gè)人標(biāo)注太奢侈但對(duì)于畢設(shè)來說哪怕只有你自己一個(gè)人標(biāo)注也建議隔一段時(shí)間后重標(biāo)一次然后對(duì)比兩次標(biāo)注的一致性。我們當(dāng)時(shí)第二遍重標(biāo)的時(shí)候發(fā)現(xiàn)有接近5%的圖像兩次標(biāo)注結(jié)果不一致這些圖像基本集中在模糊和遮擋比較嚴(yán)重的區(qū)域。把它們剔除之后模型的訓(xùn)練過程明顯穩(wěn)定了很多。另外一個(gè)我強(qiáng)烈建議的操作是在標(biāo)注完成之后按照類別分別隨機(jī)抽取一部分圖像人工快速瀏覽一遍確認(rèn)是否有標(biāo)錯(cuò)的異常值。這個(gè)檢查看起來沒有技術(shù)含量但能阻止臟數(shù)據(jù)進(jìn)入訓(xùn)練流程。很多同學(xué)把時(shí)間花在調(diào)學(xué)習(xí)率上卻不愿意花半小時(shí)做數(shù)據(jù)檢查這完全搞反了優(yōu)先級(jí)。2.3 數(shù)據(jù)增強(qiáng)怎么做才不會(huì)引入問題數(shù)據(jù)增強(qiáng)是圖像分類任務(wù)里繞不開的環(huán)節(jié)。它的本質(zhì)是對(duì)原始圖像做一系列隨機(jī)變換生成更多樣的訓(xùn)練樣本從而提升模型的泛化能力。對(duì)于動(dòng)物疲勞識(shí)別這個(gè)場(chǎng)景我們用到的增強(qiáng)操作包括水平翻轉(zhuǎn)這個(gè)操作對(duì)于動(dòng)物圖像來說是安全的因?yàn)樽笥曳D(zhuǎn)不會(huì)改變疲勞語義隨機(jī)裁剪在原始圖像上隨機(jī)裁剪一部分后縮放到固定尺寸可以模擬不同的取景范圍亮度與對(duì)比度擾動(dòng)適當(dāng)模擬光線條件變化輕微旋轉(zhuǎn)在正負(fù)15度范圍內(nèi)隨機(jī)旋轉(zhuǎn)模擬拍攝角度的小幅波動(dòng)。這里有一個(gè)很重要的原則增強(qiáng)操作不能改變疲勞語義。比如垂直翻轉(zhuǎn)就不能用因?yàn)閯?dòng)物如果倒過來特征分布完全沒有意義了。還比如過度的隨機(jī)裁剪如果裁掉了動(dòng)物主體模型學(xué)到的就是背景特征這也會(huì)出問題。所以我們裁剪的范圍通??刂圃谠紙D的0.7到1.0之間并且要保證裁剪后圖像里動(dòng)物主體仍然可見。增強(qiáng)策略還要注意訓(xùn)練集和驗(yàn)證集的區(qū)別。驗(yàn)證集和測(cè)試集一定不要做任何數(shù)據(jù)增強(qiáng)最多做一個(gè)尺寸歸一化和歸一化處理否則你評(píng)估的就是增強(qiáng)后的數(shù)據(jù)性能而不是模型本身的能力。這是一個(gè)很基礎(chǔ)但容易被忽略的細(xì)節(jié)。預(yù)處理方面統(tǒng)一的圖像尺寸我們定為224乘224像素這也是主流圖像分類模型的標(biāo)準(zhǔn)輸入大小。像素值歸一化到0到1區(qū)間方便模型訓(xùn)練時(shí)梯度穩(wěn)定。如果你用的是預(yù)訓(xùn)練模型建議直接用它在ImageNet上統(tǒng)計(jì)的均值和標(biāo)準(zhǔn)差做標(biāo)準(zhǔn)化而不是自己重新統(tǒng)計(jì)這樣遷移效果更穩(wěn)。3. CNN模型設(shè)計(jì)與核心參數(shù)選擇3.1 從零搭建還是用預(yù)訓(xùn)練模型這是每個(gè)做圖像分類的人都會(huì)面臨的選擇題。我給出的建議是如果你手里只有幾千張圖像優(yōu)先使用預(yù)訓(xùn)練模型做遷移學(xué)習(xí)如果數(shù)據(jù)集到了幾萬張的規(guī)模從零搭建的CNN也能有不錯(cuò)的表現(xiàn)。為什么這么說因?yàn)轭A(yù)訓(xùn)練模型在大型通用圖像數(shù)據(jù)集上學(xué)到過豐富的底層特征比如邊緣、紋理、形狀。這些特征對(duì)動(dòng)物圖像同樣有效你只需要讓模型在動(dòng)物疲勞數(shù)據(jù)上微調(diào)高層特征即可。從零搭建的CNN需要從隨機(jī)初始化開始學(xué)習(xí)所有特征在數(shù)據(jù)量不足時(shí)容易欠擬合或過擬合。不過從畢設(shè)角度出發(fā)我還是建議把兩種方案都跑一遍做一個(gè)對(duì)比實(shí)驗(yàn)。這個(gè)對(duì)比本身就是工作量也是論文里可以寫的一章內(nèi)容。我們的實(shí)驗(yàn)中預(yù)訓(xùn)練殘差網(wǎng)絡(luò)在驗(yàn)證集上的準(zhǔn)確率比從零搭建的小CNN高了大約4到5個(gè)百分點(diǎn)尤其是在疲勞類別上的召回率提升更明顯。如果你設(shè)備資源有限也不需要擔(dān)心。預(yù)訓(xùn)練模型可以用CPU訓(xùn)練只是慢一些。很多同學(xué)以為深度學(xué)習(xí)必須要有好顯卡其實(shí)對(duì)于二分類任務(wù)、小數(shù)據(jù)集來說CPU也完全可以跑只不過一次訓(xùn)練可能要幾個(gè)小時(shí)。我后面給出的訓(xùn)練策略里會(huì)提到如果速度太慢可以減少訓(xùn)練輪次、降低輸入分辨率這些都是可接受的折中方案。3.2 具體網(wǎng)絡(luò)結(jié)構(gòu)示例如果你想從零搭建一個(gè)輕量的CNN我給你一個(gè)我實(shí)測(cè)過非常穩(wěn)的結(jié)構(gòu)方案。輸入是224乘224的RGB圖像經(jīng)過下面這些層卷積層1輸出通道32卷積核大小3乘3填充1激活函數(shù)ReLU后面接2乘2最大池化 卷積層2輸出通道64卷積核大小3乘3填充1激活函數(shù)ReLU后面接2乘2最大池化 卷積層3輸出通道128卷積核大小3乘3填充1激活函數(shù)ReLU后面接2乘2最大池化 展平后接全連接層256維加ReLU和Dropout最后接一個(gè)2節(jié)點(diǎn)的全連接分類層輸出對(duì)應(yīng)正常和疲勞兩個(gè)類別的邏輯回歸值。這個(gè)結(jié)構(gòu)的整體參數(shù)量在百萬量級(jí)對(duì)畢設(shè)來說完全夠用。如果你用PyTorch實(shí)現(xiàn)核心代碼大概長(zhǎng)這樣import torch.nn as nn class AnimalFatigueCNN(nn.Module): def __init__(self, num_classes2): super(AnimalFatigueCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2) ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 28 * 28, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x注意這里全連接層的輸入維度是根據(jù)前面特征圖尺寸算出來的。224輸入經(jīng)過三次2倍池化之后變成28乘28再乘上卷積通道數(shù)128就是128乘28乘28。很多同學(xué)在復(fù)現(xiàn)時(shí)最容易在這里報(bào)錯(cuò)所以一開始就把計(jì)算過程理清楚。如果你選擇預(yù)訓(xùn)練模型路線可以把模型最后一層全連接替換成輸出維度為2的新全連接層其他部分沿用預(yù)訓(xùn)練權(quán)重。這些模型在深度學(xué)習(xí)框架里都有直接加載的方式不需要自己重新實(shí)現(xiàn)。訓(xùn)練時(shí)凍結(jié)前幾層參數(shù)、只微調(diào)后面幾層的方式在數(shù)據(jù)量比較小的時(shí)候效果更穩(wěn)。3.3 損失函數(shù)、優(yōu)化器與超參數(shù)設(shè)定二分類任務(wù)的損失函數(shù)首選交叉熵?fù)p失。PyTorch里的CrossEntropyLoss已經(jīng)結(jié)合了Softmax不需要在網(wǎng)絡(luò)最后一層再手動(dòng)加激活函數(shù)。如果類別不平衡可以給損失函數(shù)傳入類別權(quán)重讓少數(shù)類樣本的錯(cuò)誤產(chǎn)生更大的梯度。我們當(dāng)時(shí)的正常樣本數(shù)量大約是疲勞樣本的3.5倍所以在CrossEntropyLoss里給疲勞類設(shè)置的權(quán)重是3.5給正常類的權(quán)重是1.0。這個(gè)簡(jiǎn)單的操作讓疲勞類別的召回率明顯提升。優(yōu)化器方面我推薦使用Adam初始學(xué)習(xí)率設(shè)置為0.001。Adam自帶自適應(yīng)學(xué)習(xí)率調(diào)整對(duì)于第一次做深度學(xué)習(xí)項(xiàng)目的同學(xué)來說使用門檻比SGD低很多不需要手動(dòng)調(diào)整動(dòng)量等參數(shù)。如果用的是預(yù)訓(xùn)練模型遷移學(xué)習(xí)學(xué)習(xí)率可以降到0.0001因?yàn)槟P鸵呀?jīng)在一個(gè)比較好的初始點(diǎn)上學(xué)習(xí)率太大會(huì)破壞已經(jīng)學(xué)好的特征。批次大小我設(shè)置為32。這個(gè)數(shù)值在大部分顯卡甚至CPU上都能跑得動(dòng)同時(shí)梯度更新也比較穩(wěn)定。不要為了追求速度把批次設(shè)得過大對(duì)于這種小數(shù)據(jù)集批次64以下效果差異不大但一旦內(nèi)存溢出就很麻煩。訓(xùn)練輪次我們先設(shè)定為30到50輪配合早停機(jī)制使用。所謂早停就是每一輪訓(xùn)練后在驗(yàn)證集上計(jì)算準(zhǔn)確率如果連續(xù)若干輪驗(yàn)證準(zhǔn)確率不再提升就提前終止訓(xùn)練防止過擬合。這個(gè)機(jī)制在實(shí)際訓(xùn)練中非常有用我們?cè)诘?5輪左右觀察到了明顯的驗(yàn)證損失上升趨勢(shì)如果不早停繼續(xù)訓(xùn)練到50輪模型在測(cè)試集上的表現(xiàn)反而會(huì)下降。如果你想完全復(fù)現(xiàn)我的實(shí)驗(yàn)條件還需要設(shè)置隨機(jī)種子。PyTorch和Python的隨機(jī)數(shù)生成器、NumPy的隨機(jī)數(shù)生成器都要設(shè)置同一個(gè)種子。這樣不同人運(yùn)行同一個(gè)腳本能得到一模一樣的結(jié)果這在畢業(yè)論文里是一個(gè)可復(fù)現(xiàn)性加分項(xiàng)。代碼很簡(jiǎn)單就是固定seed為某個(gè)整數(shù)然后設(shè)置環(huán)境變量和各個(gè)庫(kù)的種子這里不展開。4. 訓(xùn)練實(shí)操與避坑記錄4.1 數(shù)據(jù)劃分與訓(xùn)練策略數(shù)據(jù)劃分方式會(huì)直接影響你對(duì)模型效果的判斷。我們當(dāng)時(shí)把數(shù)據(jù)集按7比2比1劃分為訓(xùn)練集、驗(yàn)證集、測(cè)試集。很多同學(xué)只劃分訓(xùn)練集和測(cè)試集沒有單獨(dú)的驗(yàn)證集然后在測(cè)試集上調(diào)參數(shù)這是非常大的錯(cuò)誤。調(diào)參過程本身已經(jīng)利用了測(cè)試集的信息最后得到的準(zhǔn)確率是偏樂觀的不能真實(shí)反映模型泛化能力。分出一個(gè)驗(yàn)證集專門用來調(diào)參和早停測(cè)試集只在最終評(píng)估時(shí)使用一次結(jié)果才可信。劃分的時(shí)候還要注意按個(gè)體劃分而不是簡(jiǎn)單隨機(jī)劃分。什么意思呢如果同一個(gè)動(dòng)物的多張圖像同時(shí)出現(xiàn)在訓(xùn)練集和測(cè)試集里模型可能記住了這個(gè)動(dòng)物的特征而不是學(xué)到通用的疲勞特征測(cè)試集準(zhǔn)確率會(huì)虛高。我們當(dāng)時(shí)按動(dòng)物個(gè)體先分好組確保同一個(gè)體的圖像只出現(xiàn)在一個(gè)集合里。這個(gè)細(xì)節(jié)乍一看不重要但答辯時(shí)如果評(píng)委問“你的模型是不是真的泛化到了新動(dòng)物”你就能給出一個(gè)站得住的答案。訓(xùn)練過程中我習(xí)慣每一輪都打印訓(xùn)練損失、訓(xùn)練準(zhǔn)確率、驗(yàn)證損失、驗(yàn)證準(zhǔn)確率四組數(shù)據(jù)。不要只看準(zhǔn)確率損失下降曲線更能反映模型是否收斂。很多情況下準(zhǔn)確率看起來沒變化但損失還在緩慢下降說明模型仍在學(xué)習(xí)反過來準(zhǔn)確率很高但驗(yàn)證損失開始上升說明已經(jīng)在過擬合了。如果資源允許每次訓(xùn)練結(jié)束以后保存最優(yōu)模型權(quán)重而不是最后一輪的權(quán)重。因?yàn)樽詈笠惠喛赡芤呀?jīng)過擬合或者開始波動(dòng)最優(yōu)權(quán)重通常是驗(yàn)證準(zhǔn)確率最高的那個(gè)時(shí)刻的權(quán)重。我在訓(xùn)練腳本里加入了一個(gè)簡(jiǎn)單的判斷當(dāng)驗(yàn)證準(zhǔn)確率超過歷史最佳值時(shí)保存當(dāng)前模型參數(shù)。這個(gè)策略簡(jiǎn)單但非常有效。4.2 訓(xùn)練曲線怎么看訓(xùn)練曲線是診斷模型狀態(tài)最重要的工具。我一般會(huì)在訓(xùn)練結(jié)束后把損失曲線和準(zhǔn)確率曲線畫在一張圖上直觀地觀察模型行為。常見的情況有幾種。第一種是訓(xùn)練損失下降驗(yàn)證損失也在下降這是最健康的狀態(tài)說明模型在學(xué)習(xí)且沒有過擬合。第二種是訓(xùn)練損失下降驗(yàn)證損失先降后升說明模型開始過擬合了。這時(shí)候可以回調(diào)早停、增加Dropout、增加數(shù)據(jù)增強(qiáng)或者減少模型參數(shù)。第三種是訓(xùn)練損失和驗(yàn)證損失都幾乎不降這說明模型容量不足或者學(xué)習(xí)率設(shè)置不合理需要增加網(wǎng)絡(luò)深度、寬度或者調(diào)大學(xué)習(xí)率。第四種情況比較隱蔽訓(xùn)練準(zhǔn)確率很高但驗(yàn)證準(zhǔn)確率一直在某個(gè)較低水平徘徊。這通常是數(shù)據(jù)劃分出了問題比如訓(xùn)練集和驗(yàn)證集分布不一致或者驗(yàn)證集數(shù)據(jù)量太小、噪聲太大。我遇到過類似的情況后來檢查發(fā)現(xiàn)驗(yàn)證集里有幾張圖像存在標(biāo)注錯(cuò)誤把它們剔除之后驗(yàn)證準(zhǔn)確率正常了。所以看到曲線異常先懷疑數(shù)據(jù)和標(biāo)注再去動(dòng)模型。我強(qiáng)烈建議你養(yǎng)成記錄每次實(shí)驗(yàn)的參數(shù)和結(jié)果的習(xí)慣。我當(dāng)時(shí)用了一個(gè)簡(jiǎn)單的表格記錄學(xué)習(xí)率、模型結(jié)構(gòu)、是否用預(yù)訓(xùn)練、數(shù)據(jù)增強(qiáng)方式、驗(yàn)證準(zhǔn)確率等關(guān)鍵信息。這樣做的好處是調(diào)參不再靠感覺每次修改都有據(jù)可查。很多同學(xué)調(diào)參沒過一會(huì)兒就忘了自己試過什么后期總結(jié)實(shí)驗(yàn)部分的時(shí)候才知道有多痛苦。4.3 常見問題與排查方法訓(xùn)練過程中我遇到的典型問題整理成表格給你參考癥狀可能原因排查方向損失一直不下降學(xué)習(xí)率過大或過小嘗試0.01、0.001、0.0001梯度訓(xùn)練準(zhǔn)確率正常驗(yàn)證準(zhǔn)確率低過擬合檢查是否缺驗(yàn)證集、增強(qiáng)策略、Dropout正常類別準(zhǔn)確率高疲勞類別很低類別不平衡給損失函數(shù)加類別權(quán)重或?qū)ι贁?shù)類過采樣換一批圖像后效果崩掉訓(xùn)練數(shù)據(jù)和實(shí)際場(chǎng)景差異大增加自采數(shù)據(jù)統(tǒng)一拍攝條件預(yù)測(cè)結(jié)果總偏向某一個(gè)類別閾值或類別權(quán)重問題調(diào)整決策閾值或重新設(shè)計(jì)損失權(quán)重還有一個(gè)容易被忽視的問題圖像的EXIF信息里的方向標(biāo)記。手機(jī)拍攝的照片有時(shí)會(huì)帶有旋轉(zhuǎn)標(biāo)記直接讀圖時(shí)一些庫(kù)不會(huì)自動(dòng)處理方向?qū)е聢D像被旋轉(zhuǎn)了90度。訓(xùn)練和測(cè)試時(shí)旋轉(zhuǎn)方向不一致模型自然表現(xiàn)不穩(wěn)定。解決辦法是在數(shù)據(jù)預(yù)處理階段統(tǒng)一將所有圖像轉(zhuǎn)換成RGB并校正方向輸出為標(biāo)準(zhǔn)的三通道圖像。推理階段也常遇到一個(gè)問題輸入圖像的尺寸和訓(xùn)練時(shí)不一致。如果你訓(xùn)練時(shí)統(tǒng)一縮放到224乘224那么推理時(shí)也必須走同樣的預(yù)處理流程不能直接拿一張1920乘1080的原圖丟給模型。我的做法是把預(yù)處理邏輯寫成一個(gè)獨(dú)立的函數(shù)訓(xùn)練和推理共用同一個(gè)函數(shù)避免兩邊出現(xiàn)差異。這些都是很小的細(xì)節(jié)但往往就是這些細(xì)節(jié)決定了最終成績(jī)。5. 模型評(píng)估與結(jié)果分析5.1 評(píng)價(jià)指標(biāo)怎么選二分類任務(wù)的評(píng)價(jià)指標(biāo)不只有準(zhǔn)確率。在動(dòng)物疲勞識(shí)別場(chǎng)景下準(zhǔn)確率甚至不是最重要的指標(biāo)。設(shè)想一下如果目的是篩選出疲勞狀態(tài)的動(dòng)物我們更關(guān)心的是“把疲勞動(dòng)物漏掉”的概率有多低也就是疲勞類別的召回率。反過來如果資源有限、不可能對(duì)所有標(biāo)記疲勞的個(gè)體都查驗(yàn)?zāi)敲淳_率也很重要。所以我建議你至少報(bào)告四個(gè)指標(biāo)準(zhǔn)確率、精確率、召回率、F1值并且分別計(jì)算正常類別和疲勞類別各自的數(shù)值而不是只給一個(gè)總的準(zhǔn)確率?;煜仃囈惨嫵鰜硭苤庇^地展示模型在哪一類上犯錯(cuò)更多。我們?cè)跍y(cè)試集上的最終結(jié)果顯示預(yù)訓(xùn)練模型的整體準(zhǔn)確率約為93%但把混淆矩陣拆開看正常類的召回率是96%疲勞類的召回率只有88%。這說明疲勞類被漏檢的概率仍然偏高。針對(duì)這個(gè)情況我們通過調(diào)整分類閾值將決策邊界向疲勞類別偏移在疲勞類召回率提升到92%的同時(shí)正常類召回率小幅下降到93%整體F1值更均衡了。這個(gè)調(diào)整操作在論文里可以寫得很有說服力因?yàn)槟阋呀?jīng)從“模型訓(xùn)練”進(jìn)入了“模型決策”層面的優(yōu)化。5.2 測(cè)試結(jié)果實(shí)測(cè)記錄這里給出我們一組實(shí)際的測(cè)試記錄數(shù)據(jù)來自一個(gè)模擬場(chǎng)景下的動(dòng)物圖像集類別是二分類。需要注意這組數(shù)據(jù)僅供你理解評(píng)估流程不同數(shù)據(jù)分布下結(jié)果一定會(huì)有差異你的任務(wù)是把評(píng)估方法做對(duì)而不是套用某一個(gè)準(zhǔn)確率數(shù)值。在測(cè)試集共400張圖像上正常類200張疲勞類200張這里為了展示效果我們做了類別均衡處理實(shí)際中不需要強(qiáng)行均衡測(cè)試集保持原始分布更真實(shí)。指標(biāo)數(shù)值整體準(zhǔn)確率92.5%正常類精確率94.1%正常類召回率96.0%疲勞類精確率89.2%疲勞類召回率88.0%宏平均F10.915可以看到疲勞類精確率明顯低于正常類。這說明模型把一部分正常圖像誤判成了疲勞。我逐個(gè)查看誤判圖像后發(fā)現(xiàn)最常見的情況是圖像里動(dòng)物正在閉眼休息或者低頭吃食單從視覺特征上看確實(shí)和疲勞比較接近。這種情況下模型犯錯(cuò)其實(shí)情有可原因?yàn)橹粦{單張靜態(tài)圖像人類也不能做到百分之百準(zhǔn)確。這個(gè)觀察讓我意識(shí)到如果后續(xù)想要進(jìn)一步提升效果引入視頻時(shí)序信息會(huì)是一個(gè)合理方向。當(dāng)然畢設(shè)階段做到這一步已經(jīng)很完整了時(shí)序模型可以作為展望章節(jié)的內(nèi)容來寫。5.3 誤判案例拆解誤判案例分析是論文里很出彩的部分因?yàn)樗荏w現(xiàn)你對(duì)問題本質(zhì)的理解。我舉一個(gè)我們遇到的典型例子一張動(dòng)物的側(cè)面照眼睛睜得很大按標(biāo)注規(guī)則應(yīng)該判為正常但模型輸出了疲勞類別。我仔細(xì)對(duì)比了同類圖像發(fā)現(xiàn)模型學(xué)到的疲勞特征里可能包含“頭部低垂耳朵下垂”的組合而這張圖像里動(dòng)物的耳朵恰好是向后壓的物理位置上和疲勞樣本比較像因此模型發(fā)生了混淆。這類問題很難單純靠調(diào)參解決更可行的路徑是增加類似硬樣本的訓(xùn)練數(shù)據(jù)或者在預(yù)處理階段把耳朵區(qū)域單獨(dú)裁剪出來作為輔助輸入。當(dāng)然這些都是畢設(shè)之后可以繼續(xù)做的方向不需要在一開始的版本里實(shí)現(xiàn)。與之相反的另一類誤判是圖像里動(dòng)物眼睛明顯半閉但模型判成了正常。這類誤判更危險(xiǎn)因?yàn)樗锹z。我分析后發(fā)現(xiàn)這些圖像里動(dòng)物處于側(cè)后方角度眼睛區(qū)域被毛發(fā)的陰影覆蓋視覺特征非常弱。說明模型對(duì)眼睛局部區(qū)域的敏感性還不夠。解決思路有兩個(gè)一是用注意力機(jī)制讓模型更關(guān)注眼睛和耳朵區(qū)域二是使用目標(biāo)檢測(cè)先定位頭部區(qū)域再做分類。這兩種方法都會(huì)顯著增加項(xiàng)目的復(fù)雜度所以我當(dāng)時(shí)沒有在畢設(shè)主流程中加入而是留在了改進(jìn)方案里。6. 模型部署與后續(xù)擴(kuò)展6.1 模型導(dǎo)出與推理封裝畢設(shè)如果只停留在訓(xùn)練腳本和準(zhǔn)確率報(bào)告里總覺得少了點(diǎn)東西。我建議至少做一個(gè)簡(jiǎn)單的推理腳本讓老師或者同學(xué)能直接拿一張新圖像傳入模型輸出分類結(jié)果和置信度。這個(gè)動(dòng)作不復(fù)雜但會(huì)讓整個(gè)項(xiàng)目的完整度提升一個(gè)檔次。我用PyTorch為例說一下導(dǎo)出流程。訓(xùn)練結(jié)束后把模型狀態(tài)字典加載回模型結(jié)構(gòu)然后調(diào)用eval模式。需要特別注意的是一定要在eval模式下推理否則BN層和Dropout層的行為會(huì)和訓(xùn)練時(shí)不一致。我在沒加eval模式的時(shí)候推理結(jié)果忽好忽壞查了半天才發(fā)現(xiàn)是這地方的小問題。如果想讓推理速度更快、脫離深度學(xué)習(xí)框架環(huán)境運(yùn)行可以把模型導(dǎo)出為ONNX格式然后使用ONNX Runtime加載。ONNX是一個(gè)通用的模型交換格式很多平臺(tái)都能直接運(yùn)行。部署到邊緣設(shè)備的時(shí)候也可以轉(zhuǎn)成更輕量的格式但畢設(shè)階段做到ONNX這一步已經(jīng)完全夠用。一個(gè)最簡(jiǎn)單的推理流程大概是讀取圖像統(tǒng)一縮放和歸一化輸入模型得到兩個(gè)類別的得分再經(jīng)過Softmax轉(zhuǎn)成概率取概率較大的類別作為輸出同時(shí)打印出置信度。我還建議在推理腳本里加入一個(gè)邊界情況判斷如果輸入圖像是灰度圖先轉(zhuǎn)換成三通道RGB如果圖像里沒有動(dòng)物主體直接提示用戶重新輸入不要硬跑模型輸出一個(gè)沒有意義的結(jié)果。這些交互細(xì)節(jié)看似不起眼但對(duì)于一個(gè)需要演示的項(xiàng)目來說非常重要因?yàn)楝F(xiàn)場(chǎng)演示出問題的概率其實(shí)很高提前加好防御邏輯能幫你避免尷尬。6.2 后續(xù)還能怎么擴(kuò)展畢設(shè)答完以后如果你對(duì)這個(gè)方向還有興趣可以沿著幾個(gè)方向繼續(xù)深入。最直接的方向是增加類別把二分類擴(kuò)展為“正常、輕度疲勞、重度疲勞”三分類配合更細(xì)的數(shù)據(jù)標(biāo)注規(guī)范。第二個(gè)方向是引入時(shí)序信息用視頻片段而不是單張圖像來判斷疲勞狀態(tài)通過幀序列捕捉動(dòng)物的動(dòng)態(tài)行為特征比如反應(yīng)速度、活動(dòng)頻率這些在靜態(tài)圖像里完全無法體現(xiàn)。第三個(gè)方向是把區(qū)域感知和分類結(jié)合先檢測(cè)動(dòng)物頭部、眼睛區(qū)域然后對(duì)這個(gè)局部區(qū)域做疲勞判斷理論上能降低背景干擾。還有一個(gè)我比較看好的方向是遷移到不同動(dòng)物種類上。你可以在一個(gè)動(dòng)物種類上訓(xùn)練好模型然后通過少量數(shù)據(jù)和微調(diào)把它適配到另一個(gè)種類這樣可以大幅降低人工標(biāo)注成本。這個(gè)方向在真實(shí)應(yīng)用場(chǎng)景中價(jià)值很高也適合作為后續(xù)研究?jī)?nèi)容寫進(jìn)論文展望。做這個(gè)項(xiàng)目的整個(gè)過程里我個(gè)人最大的體會(huì)是模型的代碼實(shí)現(xiàn)反而是整個(gè)流程里最簡(jiǎn)單的一環(huán)真正花時(shí)間的、決定成敗的永遠(yuǎn)是數(shù)據(jù)定義、數(shù)據(jù)處理和評(píng)估設(shè)計(jì)。很多同學(xué)一上來就想用最高級(jí)的網(wǎng)絡(luò)結(jié)構(gòu)、最流行的訓(xùn)練技巧結(jié)果忽略了最基本的數(shù)據(jù)質(zhì)量檢查最后模型泛化能力一塌糊涂。如果你準(zhǔn)備做這個(gè)題目我真心建議你把一半以上的時(shí)間花在數(shù)據(jù)上先把數(shù)據(jù)和評(píng)估體系做扎實(shí)再談模型結(jié)構(gòu)你會(huì)發(fā)現(xiàn)問題少很多。最后分享一個(gè)小技巧把所有實(shí)驗(yàn)的超參數(shù)、結(jié)果、踩坑記錄都寫在一個(gè)單獨(dú)的文檔里包括失敗了的東西也記下來。答辯的時(shí)候評(píng)委問“你有沒有嘗試過其他方案”你就能自然地講出來“試過效果不好原因是什么”。這種回答展示的是真實(shí)的研究過程遠(yuǎn)遠(yuǎn)比只講成功的部分更有說服力。希望這篇內(nèi)容能幫你在畢設(shè)路上少走幾個(gè)彎路也祝你最后能交出一份自己滿意的項(xiàng)目。