:序列數(shù)據(jù)分類從訓(xùn)練到部署全實(shí)踐)
簡介基于MATLAB的一維卷積神經(jīng)網(wǎng)絡(luò)識別系統(tǒng)設(shè)計(jì)是一份面向深度學(xué)習(xí)初學(xué)者以及信號處理、音頻分析等從業(yè)者的完整工程資源主要解決一維序列數(shù)據(jù)在模式識別與分類任務(wù)中的落地問題。壓縮包共包含7個(gè)文件以腳本文件、數(shù)據(jù)文件、文本說明為主要類型整個(gè)資源包約217KB腳本負(fù)責(zé)模型構(gòu)建與數(shù)據(jù)預(yù)處理數(shù)據(jù)文件保存訓(xùn)練樣本和已經(jīng)訓(xùn)練好的網(wǎng)絡(luò)參數(shù)文本文件則記錄實(shí)驗(yàn)過程中的參數(shù)設(shè)置與代碼注釋有助于理解并復(fù)現(xiàn)完整流程。目前已有388人學(xué)習(xí)瀏覽。該資源從數(shù)據(jù)加載、歸一化處理、模型搭建、超參數(shù)調(diào)整、訓(xùn)練評估到模型保存以及可視化界面的使用覆蓋了一維卷積神經(jīng)網(wǎng)絡(luò)項(xiàng)目的核心環(huán)節(jié)。借助自帶的訓(xùn)練后網(wǎng)絡(luò)模型讀者可直接進(jìn)行預(yù)測或遷移學(xué)習(xí)也可以在此模板上修改網(wǎng)絡(luò)結(jié)構(gòu)和參數(shù)開展其他序列數(shù)據(jù)挖掘任務(wù)兼具教學(xué)參考與二次開發(fā)價(jià)值。1. 一個(gè)值得復(fù)現(xiàn)的MATLAB一維CNN識別系統(tǒng)從文件到訓(xùn)練再到部署的完整鏈路我是在調(diào)音頻信號分類的時(shí)候發(fā)現(xiàn)這個(gè)項(xiàng)目的。手里有一批傳感器采集的一維序列數(shù)據(jù)長度幾千點(diǎn)類別有七八種之前用特征工程加SVM做到頭也就是九成出頭換了一維CNN直接推到98%以上。這個(gè)基于MATLAB的一維CNN識別系統(tǒng)設(shè)計(jì)核心就是把一維卷積神經(jīng)網(wǎng)絡(luò)用在序列數(shù)據(jù)上完整覆蓋了數(shù)據(jù)加載、預(yù)處理、模型構(gòu)建、訓(xùn)練、模型保存到GUI可視化整個(gè)流程。適合兩類人一是正在做音頻、振動、肌電、光譜這類一維信號分類的從業(yè)者想看看MATLAB里1DCNN的工程實(shí)現(xiàn)長什么樣二是剛接觸深度學(xué)習(xí)、手里只有MATLAB不想折騰Python環(huán)境的學(xué)生。整個(gè)項(xiàng)目文件不大但trainCnn.m、getData.m、data.mat、兩個(gè)訓(xùn)練好的網(wǎng)絡(luò)模型加兩個(gè)文本說明文件把一條完整的技術(shù)鏈路擺得很清楚值得花一個(gè)下午逐行拆一遍。2. 拆開文件清單再看1DCNN選型為什么這個(gè)場景用一維卷積而不是LSTM或SVM2.1 文件結(jié)構(gòu)和每個(gè)文件的定位先看壓縮包里到底有什么。trainCnn.m是整個(gè)系統(tǒng)的訓(xùn)練入口負(fù)責(zé)構(gòu)建網(wǎng)絡(luò)、設(shè)置訓(xùn)練參數(shù)、跑訓(xùn)練循環(huán)getData.m是數(shù)據(jù)預(yù)處理腳本從data.mat里把數(shù)據(jù)讀出來做歸一化或標(biāo)準(zhǔn)化再劃分訓(xùn)練集驗(yàn)證集cnnNet.mat和net.mat是兩個(gè)已經(jīng)訓(xùn)練完的模型存檔trainCnn.txt和getData.txt是對應(yīng)的代碼說明或參數(shù)備注data.mat是存放原始數(shù)據(jù)和標(biāo)簽的MATLAB數(shù)據(jù)文件。這個(gè)組織結(jié)構(gòu)非常典型——訓(xùn)練腳本和數(shù)據(jù)預(yù)處理分開模型單獨(dú)存盤說明文本緊跟著代碼走。% 查看data.mat里的變量結(jié)構(gòu) load(data.mat, -mat); whos(-file, data.mat);用whos命令先看data.mat里有什么變量、什么維度、什么類型這是接手任何MATLAB深度學(xué)習(xí)項(xiàng)目的第一步。上面這段命令不會加載數(shù)據(jù)到工作區(qū)只是列出文件內(nèi)的變量信息省內(nèi)存也不會污染當(dāng)前工作區(qū)。常見情況是data.mat里面存著data和label兩個(gè)變量data維度可能是numChannels×numObservations或numObservations×sequenceLength看你數(shù)據(jù)本身的組織方式而定。2.2 為什么一維CNN適合序列識別任務(wù)一維CNN和一維序列數(shù)據(jù)是天然的匹配關(guān)系。二維CNN在圖像上通過卷積核捕捉局部空間特征一維CNN做的事完全一樣只是把卷積核從二維壓成一維沿著序列長度方向滑動捕捉的是局部的時(shí)序模式。相比LSTM1DCNN訓(xùn)練速度快、參數(shù)少、不容易過擬合而且在特征提取階段是并行計(jì)算的對中短長度的序列數(shù)據(jù)性價(jià)比非常高。相比手工特征加SVMCNN把特征提取和分類合成了一個(gè)端到端模型不用再糾結(jié)提取哪些統(tǒng)計(jì)特征、要不要做小波包分解、SVM的核函數(shù)選RBF還是多項(xiàng)式。% 一維CNN的典型層結(jié)構(gòu) layers [ sequenceInputLayer(1) convolution1dLayer(16, 32, Padding, same) batchNormalizationLayer reluLayer maxPooling1dLayer(2, Stride, 2) convolution1dLayer(8, 64, Padding, same) batchNormalizationLayer reluLayer globalAveragePooling1dLayer fullyConnectedLayer(numClasses) softmaxLayer classificationLayer];sequenceInputLayer(1)表示每個(gè)時(shí)間步只有一個(gè)通道的輸入如果你的數(shù)據(jù)是多通道的比如三軸加速度傳感器或兩通道肌電就把1改成對應(yīng)通道數(shù)。convolution1dLayer(16, 32)這里第一個(gè)參數(shù)16是卷積核長度第二個(gè)32是濾波器個(gè)數(shù)也就是輸出通道數(shù)。Padding, same保證卷積后序列長度不變后面接池化層時(shí)才不會出現(xiàn)維度對不上的問題。globalAveragePooling1dLayer的作用是把整個(gè)序列的特征圖壓縮成一個(gè)向量替代了傳統(tǒng)的flatten加全連接的做法參數(shù)更少且不容易過擬合這個(gè)設(shè)計(jì)在序列分類里非常實(shí)用。2.3 trainCnn.m里的網(wǎng)絡(luò)定義思路實(shí)際項(xiàng)目里trainCnn.m通常不光定義了上面那套層結(jié)構(gòu)還會用analyzeNetwork來檢查網(wǎng)絡(luò)結(jié)構(gòu)、用trainingOptions設(shè)置訓(xùn)練超參數(shù)、用trainNetwork執(zhí)行訓(xùn)練。我在拆這個(gè)項(xiàng)目時(shí)比較關(guān)心的是它有沒有用layerGraph來做更復(fù)雜的結(jié)構(gòu)比如殘差連接或分支結(jié)構(gòu)如果只用簡單sequential的layers數(shù)組說明這個(gè)項(xiàng)目的網(wǎng)絡(luò)設(shè)計(jì)比較基礎(chǔ)適合作為入門模板。3. 數(shù)據(jù)管線怎么搭getData.m的數(shù)據(jù)預(yù)處理和訓(xùn)練參數(shù)設(shè)置3.1 getData.m里的預(yù)處理流程數(shù)據(jù)預(yù)處理是深度學(xué)習(xí)里最容易被低估的一環(huán)。getData.m做的事通常是這樣的流程load數(shù)據(jù)、檢查維度、歸一化、劃分訓(xùn)練集和測試集、整理成trainNetwork需要的格式。歸一化的方式選擇上我一般先看數(shù)據(jù)量綱——如果數(shù)據(jù)是電壓值或加速度值范圍可能從負(fù)數(shù)到正數(shù)用z-score標(biāo)準(zhǔn)化更合適如果是像素值或0到1之間的數(shù)據(jù)用min-max歸一化就夠了。這個(gè)項(xiàng)目里大概率用的是mapminmax或直接手動算均值方差。% getData.m中的數(shù)據(jù)預(yù)處理核心邏輯按常見做法補(bǔ)全 load(data.mat); % 假設(shè)data是numSamples×seqLengthlabel是numSamples×1 % 轉(zhuǎn)置成MATLAB深度學(xué)習(xí)工具箱要求的格式seqLength×numSamples data data; % 每個(gè)樣本是seqLength×1的序列cell數(shù)組里每個(gè)元素是一個(gè)樣本 dataCell cell(1, size(data, 2)); for i 1:size(data, 2) dataCell{i} data(:, i); end % z-score標(biāo)準(zhǔn)化按整個(gè)訓(xùn)練集的均值和標(biāo)準(zhǔn)差做 mu mean([dataCell{:}], all); sigma std([dataCell{:}], 0, all); for i 1:length(dataCell) dataCell{i} (dataCell{i} - mu) / sigma; end % 標(biāo)簽轉(zhuǎn)成categorical類型 labelCategorical categorical(label); % 劃分訓(xùn)練集和測試集8:2 cv cvpartition(length(dataCell), HoldOut, 0.2); idxTrain training(cv); idxTest test(cv); dataTrain dataCell(idxTrain); labelTrain labelCategorical(idxTrain); dataTest dataCell(idxTest); labelTest labelCategorical(idxTest);這段代碼里最關(guān)鍵的操作是維度轉(zhuǎn)置和cell數(shù)組封裝。MATLAB的trainNetwork要求序列數(shù)據(jù)以序列長度×通道數(shù)×樣本數(shù)的組織方式存但很多.mat文件里的原始數(shù)據(jù)是樣本數(shù)×序列長度不轉(zhuǎn)置直接喂進(jìn)去會得到一堆維度錯(cuò)誤。標(biāo)準(zhǔn)化用的mu和sigma必須只從訓(xùn)練集統(tǒng)計(jì)這是深度學(xué)習(xí)中一個(gè)非常經(jīng)典的細(xì)節(jié)——如果把測試集的統(tǒng)計(jì)信息混進(jìn)來驗(yàn)證結(jié)果就會虛高部署到真實(shí)數(shù)據(jù)上性能直接跳水。3.2 訓(xùn)練超參數(shù)怎么設(shè)trainCnn.m里trainingOptions的設(shè)置直接決定訓(xùn)練的質(zhì)量和時(shí)長。% trainCnn.m中的訓(xùn)練參數(shù)設(shè)置按常見做法補(bǔ)全 options trainingOptions(adam, ... InitialLearnRate, 0.001, ... LearnRateSchedule, piecewise, ... LearnRateDropPeriod, 20, ... LearnRateDropFactor, 0.5, ... MiniBatchSize, 64, ... MaxEpochs, 80, ... Shuffle, every-epoch, ... ValidationData, {dataTest, labelTest}, ... ValidationFrequency, 10, ... Verbose, true, ... Plots, training-progress, ... ExecutionEnvironment, auto);關(guān)于這段參數(shù)有幾點(diǎn)實(shí)際經(jīng)驗(yàn)值得展開。InitialLearnRate的0.001是Adam優(yōu)化器的默認(rèn)值但如果你的數(shù)據(jù)量很小或類別不平衡建議降到0.0005甚至0.0001否則容易出現(xiàn)訓(xùn)練剛開始loss就卡住不動的現(xiàn)象。MiniBatchSize和MaxEpochs是一對互相牽連的參數(shù)batch越大每個(gè)epoch的迭代次數(shù)越少同樣的epoch數(shù)下模型看到的樣本重復(fù)次數(shù)就完全不同數(shù)據(jù)量幾千條時(shí)64的batch配80個(gè)epoch是合理的起點(diǎn)數(shù)據(jù)量上萬時(shí)batch可以提到128。ValidationFrequency設(shè)成10的意思是每10次迭代跑一次驗(yàn)證集這個(gè)值設(shè)太大會導(dǎo)致訓(xùn)練過程里看不到驗(yàn)證曲線變化、無法及時(shí)判斷過擬合。ExecutionEnvironment設(shè)auto是讓MATLAB自己挑GPU還是CPU如果你的機(jī)器沒有GPU它會自動退回CPU有GPU但顯存不夠時(shí)把MiniBatchSize減半通常是第一個(gè)排查動作。3.3 訓(xùn)練和驗(yàn)證的完整流程訓(xùn)練過程本身的代碼在trainCnn.m里通常只有兩三行net trainNetwork(dataTrain, labelTrain, layers, options); save(cnnNet.mat, net);trainNetwork返回的net是一個(gè)訓(xùn)練好的網(wǎng)絡(luò)對象包含了所有層的權(quán)重、偏差和訓(xùn)練信息。save命令把網(wǎng)絡(luò)存成.mat文件下次要用時(shí)直接load就能做預(yù)測不用重新訓(xùn)練。如果trainCnn.m里訓(xùn)練完還順手畫了混淆矩陣或計(jì)算準(zhǔn)確率那這份代碼的完整性就很高了適合作為復(fù)現(xiàn)模板直接改數(shù)據(jù)就跑。4. 避坑手冊MATLAB一維CNN訓(xùn)練實(shí)戰(zhàn)中的六個(gè)常見坑4.1 sequenceInputLayer的輸入維度對不上現(xiàn)象運(yùn)行trainNetwork時(shí)報(bào)錯(cuò)提示輸入層期望的維度與實(shí)際輸入數(shù)據(jù)維度不一致錯(cuò)誤信息里出現(xiàn)類似Invalid input data的字樣。原因MATLAB深度學(xué)習(xí)工具箱要求序列輸入是N×C×S的格式而大多數(shù)人手里的原始數(shù)據(jù)是S×N每個(gè)樣本一行。解決把數(shù)據(jù)轉(zhuǎn)置后用cell數(shù)組按樣本逐個(gè)封裝。我自己的習(xí)慣是在getData.m里轉(zhuǎn)置后加一行size檢查assert(size(dataCell{1}, 1) sequenceLength, 序列長度與輸入層不匹配);這行斷言省得訓(xùn)練跑到一半才發(fā)現(xiàn)維度問題浪費(fèi)幾十分鐘。4.2 學(xué)習(xí)率設(shè)太大導(dǎo)致loss爆炸現(xiàn)象訓(xùn)練剛開始幾輪迭代loss就沖到NaN訓(xùn)練曲線直接掉到地板上。原因?qū)W習(xí)率超出數(shù)據(jù)分布能承受的范圍梯度更新步長過大參數(shù)發(fā)散。解決先把學(xué)習(xí)率降到0.0001重新跑如果還炸檢查數(shù)據(jù)里有沒有inf或極端異常值歸一化是否真的生效。一個(gè)血淚經(jīng)驗(yàn)有時(shí)候爆炸不是學(xué)習(xí)率的問題而是label里有NaN訓(xùn)練前用isfinite檢查一下標(biāo)簽數(shù)組省得排查半天方向全錯(cuò)。4.3 驗(yàn)證集準(zhǔn)確率遠(yuǎn)低于訓(xùn)練集現(xiàn)象訓(xùn)練集準(zhǔn)確率一路漲到99%以上驗(yàn)證集卡在80%上下紋絲不動。原因典型過擬合模型把訓(xùn)練集里的細(xì)節(jié)記住了而不是學(xué)到泛化規(guī)律。解決先加dropout層或增大L2正則再看是不是訓(xùn)練集和測試集劃分時(shí)有泄漏——getData.m里標(biāo)準(zhǔn)化如果用了全數(shù)據(jù)的均值和方差驗(yàn)證集被污染了這個(gè)坑比過擬合更難發(fā)現(xiàn)。排查方法是只對訓(xùn)練集計(jì)算mu和sigma然后應(yīng)用到測試集上。4.4 ValidationPatience設(shè)太小導(dǎo)致訓(xùn)練提前終止現(xiàn)象訓(xùn)練在第30個(gè)epoch左右就報(bào)Validation accuracy has reached its maximum value并自動停止。原因MATLAB的trainingOptions里默認(rèn)開啟ValidationPatience連續(xù)多次驗(yàn)證準(zhǔn)確率沒有提升就提前終止。解決如果數(shù)據(jù)本身噪聲大驗(yàn)證準(zhǔn)確率有波動是正常的把ValidationPatience從默認(rèn)值改大ValidationPatience, 20這樣允許模型在驗(yàn)證集上多徘徊幾次再下結(jié)論避免在局部波谷被誤判為收斂。4.5 GPU顯存不足現(xiàn)象報(bào)錯(cuò)Out of memory on device訓(xùn)練中斷。原因MiniBatchSize太大、序列長度太長或者GPU是幾年前的2GB老卡。解決先檢查ExecutionEnvironment是不是設(shè)了gpu但沒有判斷GPU是否可用然后把MiniBatchSize減半或縮短序列長度最直接的辦法是if gpuDeviceCount 0 options.ExecutionEnvironment gpu; else options.ExecutionEnvironment cpu; end保證代碼在沒GPU的機(jī)器上也能跑通別人復(fù)現(xiàn)時(shí)不至于一上來就卡在硬件上。4.6 .mat模型文件load之后predict維度對不上現(xiàn)象加載cnnNet.mat后做預(yù)測輸入新樣本時(shí)報(bào)維度錯(cuò)誤。原因訓(xùn)練時(shí)的輸入格式是sequenceLength×1的序列預(yù)測時(shí)也必須保持同樣的格式——不是直接喂一個(gè)1×sequenceLength的行向量而是要裝成cell數(shù)組或保持一致的維度順序。解決用zeros(sequenceLength, 1)來創(chuàng)建輸入保證與訓(xùn)練數(shù)據(jù)格式完全一致。5. 模型部署與GUI可視化訓(xùn)練完的模型如何在真實(shí)場景里跑起來5.1 模型存檔從cnnNet.mat里加載并使用訓(xùn)練完成后模型部署的第一步是把網(wǎng)絡(luò)從.mat文件里恢復(fù)到工作區(qū)然后對新樣本做預(yù)測。MATLAB里這一步很直接% 加載訓(xùn)練好的模型 loadedData load(cnnNet.mat); net loadedData.net; % 假設(shè)新來一條真實(shí)采集的序列長度要跟訓(xùn)練數(shù)據(jù)一致 newSequence sensorData; % 轉(zhuǎn)置成sequenceLength×1 newSequence (newSequence - mu) / sigma; % 用訓(xùn)練集的統(tǒng)計(jì)量標(biāo)準(zhǔn)化 newSequenceCell {newSequence}; % 預(yù)測 predScore predict(net, newSequenceCell); [~, predLabel] max(predScore);predict返回的是每個(gè)類別的概率分布取最大值對應(yīng)的下標(biāo)就是模型判定出的類別。這段代碼里最容易忽略的是標(biāo)準(zhǔn)化那一步——訓(xùn)練時(shí)用了z-score部署時(shí)新數(shù)據(jù)也必須用完全相同的mu和sigma做變換否則模型看到的分布跟訓(xùn)練時(shí)不一樣準(zhǔn)確率直接打?qū)φ?。如果?xiàng)目里連mu和sigma也一并存進(jìn).mat文件那部署流程就是閉環(huán)的這也是我建議你的trainCnn.m在save時(shí)順手把mu和sigma一起存進(jìn)去的原因。5.2 GUI可視化非專業(yè)用戶也能點(diǎn)按鈕出結(jié)果這個(gè)項(xiàng)目附帶GUI界面核心作用就是讓不會寫代碼的人也能用訓(xùn)練好的模型。GUI通常由MATLAB的App Designer或GUIDE編寫工程結(jié)構(gòu)上是回調(diào)函數(shù)驅(qū)動界面上擺著加載模型按鈕、選擇文件按鈕、開始識別按鈕和一個(gè)顯示識別結(jié)果的文本區(qū)。% GUI按鈕回調(diào)函數(shù)的核心邏輯示意 function onPredictButtonPushed(app) % 讀取界面上的文件路徑 filePath app.FilePathEditField.Value; rawData load(filePath); % 與訓(xùn)練時(shí)保持一致的預(yù)處理 seq rawData.signal(:); seq (seq - app.mu) / app.sigma; % 用存儲在網(wǎng)絡(luò)對象app.net上的模型預(yù)測 scores predict(app.net, {seq}); [~, idx] max(scores); app.ResultLabel.Text sprintf(識別結(jié)果類別%d置信度%.2f, idx, scores(idx)); end回調(diào)函數(shù)的設(shè)計(jì)要點(diǎn)是模型和預(yù)處理參數(shù)在網(wǎng)絡(luò)打開時(shí)就已經(jīng)加載到app對象里按鈕回調(diào)只需要讀文件、預(yù)處理、預(yù)測、顯示四步。好的GUI會把置信度也顯示出來因?yàn)閷?shí)際使用中低置信度的結(jié)果應(yīng)該交給人工復(fù)核這是工程落地時(shí)一個(gè)非常實(shí)用的細(xì)節(jié)。5.3 驗(yàn)證部署結(jié)果的步驟模型部署后必須做一次盲測才可信。我的做法是從訓(xùn)練時(shí)完全沒碰過的數(shù)據(jù)里抽一批新樣本逐條喂給GUI或predict函數(shù)把預(yù)測結(jié)果跟真實(shí)標(biāo)簽比對算一個(gè)混淆矩陣。如果混淆矩陣對角線明顯亮、非對角線稀疏說明模型部署成功。這一步雖然簡單但能防住很多隱藏問題——比如GUI里預(yù)處理寫錯(cuò)了、模型文件名不對導(dǎo)致load進(jìn)來的是舊版本、標(biāo)準(zhǔn)化參數(shù)用錯(cuò)等。從那以后我每次做完一個(gè)MATLAB深度學(xué)習(xí)項(xiàng)目都會強(qiáng)制走一遍這個(gè)閉環(huán)訓(xùn)練完存網(wǎng)路和標(biāo)準(zhǔn)化參數(shù)部署時(shí)從項(xiàng)目文件夾以外的地方拿新數(shù)據(jù)做盲測確認(rèn)GUI顯示的結(jié)果和predict命令行返回值完全一致再交付給下一個(gè)使用的人——希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取