習(xí)模型對(duì)比與實(shí)驗(yàn)全解析)
簡(jiǎn)介一份面向計(jì)算機(jī)專業(yè)學(xué)生、用于畢設(shè)或課程作業(yè)的深度學(xué)習(xí)教育項(xiàng)目聚焦近紅外光譜數(shù)據(jù)的回歸分析。近紅外光譜屬于非破壞性檢測(cè)技術(shù)項(xiàng)目將深度學(xué)習(xí)模型應(yīng)用于成分或性質(zhì)預(yù)測(cè)是數(shù)據(jù)科學(xué)領(lǐng)域的典型實(shí)踐。壓縮包共9個(gè)文件以8個(gè)Python腳本為主輔以1個(gè)Markdown說(shuō)明文檔整體大小僅27KB代碼結(jié)構(gòu)清晰。腳本中實(shí)現(xiàn)了ConvNet、SpectFormer、DeepVit、VitNet等模型并提供對(duì)應(yīng)的遷移學(xué)習(xí)版本便于對(duì)照不同網(wǎng)絡(luò)結(jié)構(gòu)在光譜回歸任務(wù)上的表現(xiàn)。已有235人瀏覽學(xué)習(xí)。通過(guò)閱讀這些源碼可以掌握數(shù)據(jù)預(yù)處理、模型搭建、訓(xùn)練驗(yàn)證等完整流程也可直接復(fù)用或修改模型用于自己的實(shí)驗(yàn)或課題適合想快速上手深度學(xué)習(xí)回歸項(xiàng)目的學(xué)生參考。1. 近紅外光譜數(shù)據(jù)回歸分析模型這套代碼包不是黑匣子是一組可復(fù)現(xiàn)的對(duì)照實(shí)驗(yàn)?zāi)玫健富谏疃葘W(xué)習(xí)的近紅外光譜數(shù)據(jù)回歸分析模型」這套代碼包時(shí)我第一反應(yīng)是又一個(gè)調(diào)參模板拆完才覺(jué)得它最值錢(qián)的不是某一個(gè)模型有多先進(jìn)而是同一份數(shù)據(jù)上同時(shí)給了四條結(jié)構(gòu)路線——ConvNet、VitNet、DeepVit、SpectFormer——每條還帶一個(gè)帶 TL 后綴的遷移學(xué)習(xí)變體。近紅外光譜回歸任務(wù)正好卡在幾個(gè)老問(wèn)題上波長(zhǎng)點(diǎn)太多、樣本量太少、化學(xué)吸收帶相互重疊單靠一個(gè)網(wǎng)絡(luò)很難把「為什么這樣設(shè)計(jì)」講清楚。這套結(jié)構(gòu)把最常見(jiàn)的解題路徑一次鋪開(kāi)了。適合兩種人一種是做計(jì)算機(jī)類畢設(shè)、課程設(shè)計(jì)需要完整實(shí)驗(yàn)對(duì)比的同學(xué)另一種是手里已有光譜數(shù)據(jù)、想快速評(píng)估不同網(wǎng)絡(luò)上限在哪的工程師。文件列表很干凈每個(gè)網(wǎng)絡(luò)結(jié)構(gòu)一個(gè) py 文件帶 TL 的變體單獨(dú)拆開(kāi)README.md 負(fù)責(zé)串聯(lián)入口按順序跑就能復(fù)現(xiàn)。2. 從近紅外光譜 CSV 到可訓(xùn)練張量預(yù)處理順序、劃分策略與回歸指標(biāo)近紅外光譜的輸入形式和圖像差別很大。圖像是二維空間信息光譜是一條一維曲線橫軸是波長(zhǎng)常見(jiàn) 780–2500 nm縱軸是吸光度或反射率。拿到手的數(shù)據(jù)常見(jiàn)格式是 CSV 表格存所有樣本每行一個(gè)樣本第一列是目標(biāo)值水分、蛋白、某成分含量后面幾百上千列是波長(zhǎng)點(diǎn)的吸光度也有轉(zhuǎn)置存放的。第一步不是忙著建模而是確認(rèn)數(shù)據(jù)的存儲(chǔ)方向、缺失值情況和量綱是否一致。近紅外吸收主要來(lái)自 O-H、N-H、C-H 等含氫基團(tuán)的倍頻與合頻振動(dòng)譜帶重疊嚴(yán)重這決定了后面必須做平滑和導(dǎo)數(shù)處理否則模型很容易被基線漂移和高頻噪聲帶偏。2.1 把數(shù)據(jù)讀進(jìn)來(lái)每行一個(gè)樣本字段名就是波長(zhǎng)點(diǎn)先用 pandas 把 CSV 讀進(jìn)來(lái)打印 shape 和缺失值。光譜數(shù)據(jù)經(jīng)常出現(xiàn)兩種情況某一列某個(gè)波長(zhǎng)點(diǎn)全為 0或者某幾行樣品吸光度異常偏高后者多半是樣品表面狀態(tài)不同導(dǎo)致的散射差異不能直接刪要靠預(yù)處理矯正。import pandas as pd import numpy as np # 常見(jiàn)近紅外光譜 CSV每行一個(gè)樣本第一列是目標(biāo)值其余列是波長(zhǎng)點(diǎn) data pd.read_csv(spectra.csv) print(原始形狀:, data.shape) # (樣本數(shù), 波長(zhǎng)點(diǎn)數(shù) 1) print(缺失值統(tǒng)計(jì):\n, data.isna().sum().sum()) # 分離目標(biāo)值和光譜矩陣 y data.iloc[:, 0].values.astype(float32) # 第一列是濃度/含量 X data.iloc[:, 1:].values.astype(float32) # 其余列是吸光度 # 波長(zhǎng)點(diǎn)數(shù)量直接決定模型輸入維度記下來(lái)后面設(shè)置網(wǎng)絡(luò)要用 seq_len X.shape[1] print(f樣本數(shù): {X.shape[0]}, 波長(zhǎng)點(diǎn)數(shù): {seq_len})這段代碼的邏輯是先把數(shù)據(jù)拆成X和yy是回歸目標(biāo)X是光譜矩陣形狀是(N, L)。seq_len后面做 ConvNet 和 Transformer 都要用到一維卷積的輸入長(zhǎng)度和 ViT 切 patch 的總長(zhǎng)都由它決定。注意 dtype 統(tǒng)一轉(zhuǎn)成 float32PyTorch 默認(rèn)浮點(diǎn)類型就是 float32不轉(zhuǎn)會(huì)在 loss 計(jì)算時(shí)出現(xiàn)類型不匹配的報(bào)錯(cuò)。缺失值處理上我一般先看缺失比例低于 1% 直接按列均值填充高于 5% 就查儀器采集記錄大概率是樣品問(wèn)題。2.2 預(yù)處理三步走SG 平滑、SNV、一階導(dǎo)數(shù)順序別反近紅外光譜預(yù)處理是典型的「做對(duì)了不出彩做錯(cuò)了全翻車(chē)」的環(huán)節(jié)。最常見(jiàn)的組合是 Savitzky-Golay 平滑 SNV標(biāo)準(zhǔn)正態(tài)變換 一階導(dǎo)數(shù)。SG 平滑負(fù)責(zé)去高頻噪聲SNV 逐行消除基線漂移和光程差異一階導(dǎo)數(shù)進(jìn)一步消掉基線平移。順序不能反先平滑再求導(dǎo)否則噪聲會(huì)被導(dǎo)數(shù)放大。from scipy.signal import savgol_filter # 1) SG 平滑窗口 11、多項(xiàng)式階數(shù) 2對(duì)每一條光譜單獨(dú)做 X_sg savgol_filter(X, window_length11, polyorder2, axis1) # 2) SNV按樣本行做消除基線漂移和光程差異 X_snv (X_sg - X_sg.mean(axis1, keepdimsTrue)) / X_sg.std(axis1, keepdimsTrue) # 3) 一階導(dǎo)數(shù)用 SG 在平滑的同時(shí)求導(dǎo)窗口可以小一點(diǎn) X_deriv savgol_filter(X_snv, window_length9, polyorder2, deriv1, axis1) print(預(yù)處理后形狀:, X_deriv.shape, 均值≈, X_deriv.mean().round(6))邏輯說(shuō)明三個(gè)變換全部作用于axis1也就是對(duì)每條光譜內(nèi)部做計(jì)算。window_length控制平滑窗口覆蓋的波長(zhǎng)點(diǎn)數(shù)窗口越大越平滑但會(huì)把窄的吸收峰一起抹掉近紅外譜峰的半高寬通常在 10–30 nm 左右按采樣間隔折算成點(diǎn)數(shù)后窗口取 9–15 是安全區(qū)。deriv1表示一階導(dǎo)數(shù)如果二階導(dǎo)數(shù)可以消掉基線斜率但對(duì)噪聲更敏感畢設(shè)階段先用一階就夠。預(yù)處理后的均值應(yīng)該接近 0如果均值還明顯偏離說(shuō)明 SNV 的 axis 選反了大概率是按列做的。這里補(bǔ)一句 C 的角色如果光譜矩陣到了幾十萬(wàn)行、幾百個(gè)波長(zhǎng)點(diǎn)這種規(guī)模Python 的 pandas 循環(huán)會(huì)卡在 IO 和逐行運(yùn)算上用 C 寫(xiě)數(shù)據(jù)清洗和批量切分會(huì)快不少但畢設(shè)和課程設(shè)計(jì)這個(gè)數(shù)據(jù)量Python 的 scipy 和 pandas 足夠不需要額外引入 C 模塊。2.3 劃分?jǐn)?shù)據(jù)集近紅外數(shù)據(jù)不建議直接隨機(jī)切很多人在這一步直接train_test_split(random_state42)這是近紅外回歸里最容易埋雷的地方。近紅外樣品往往來(lái)自同一批次、同一工藝相鄰編號(hào)的光譜差異極小隨機(jī)切分會(huì)把非常相似的樣本同時(shí)分進(jìn)訓(xùn)練和測(cè)試集測(cè)試集 R2 虛高到 0.99換個(gè)批次數(shù)據(jù)直接崩。近紅外領(lǐng)域的通行做法是按光譜距離劃分Kennard-Stone 算法是主流思路是每次選離已選樣本最遠(yuǎn)的樣本進(jìn)訓(xùn)練集讓測(cè)試集盡可能獨(dú)立。from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_squared_error # 快速驗(yàn)證版本先固定隨機(jī)種子保證實(shí)驗(yàn)可復(fù)現(xiàn) X_train, X_test, y_train, y_test train_test_split( X_deriv, y, test_size0.2, random_state42 ) # 正式實(shí)驗(yàn)建議用 Kennard-Stone 按光譜距離劃分 # 常見(jiàn)做法是用 ks 庫(kù)或自己實(shí)現(xiàn)距離貪婪采樣這里給出替代方案 def ks_split(X, test_size0.2): n X.shape[0] n_test int(n * test_size) selected [np.argmin(np.linalg.norm(X - X[0], axis1))] while len(selected) n - n_test: dist np.min(np.linalg.norm(X[:, None, :] - X[selected], axis2), axis1) selected.append(int(np.argmax(dist))) mask np.zeros(n, dtypebool) mask[selected] True return X[~mask], X[mask], y[~mask], y[mask]邏輯說(shuō)明train_test_split只適合快速摸流程正式跑實(shí)驗(yàn)矩陣必須換成 KS 劃分或者至少按批次分組。上面的ks_split是 Kennard-Stone 的簡(jiǎn)化實(shí)現(xiàn)np.argmax(dist)每次挑離已選樣本最遠(yuǎn)的光譜進(jìn)訓(xùn)練集保證測(cè)試集是「模型沒(méi)見(jiàn)過(guò)的光譜范圍」。這個(gè)方法在近紅外文獻(xiàn)里很常見(jiàn)缺點(diǎn)是計(jì)算距離矩陣時(shí)內(nèi)存占用高樣本量超過(guò)兩三萬(wàn)就建議用分批近似。數(shù)據(jù)劃分完后回歸指標(biāo)要同時(shí)看三個(gè)不要只盯 R2。指標(biāo)作用近紅外回歸常用參考R2決定系數(shù)看模型解釋了多大比例的方差0.95 很穩(wěn)0.90–0.95 夠用RMSEP測(cè)試集預(yù)測(cè)均方根誤差看絕對(duì)誤差越小越好必須結(jié)合 y 的量綱看RPDy 的標(biāo)準(zhǔn)差除以 RMSEP3 優(yōu)秀2.5–3 可用2 謹(jǐn)慎RMSEP 里的 P 指 Prediction專門(mén)評(píng)價(jià)測(cè)試集預(yù)測(cè)比訓(xùn)練集 RMSE 更有說(shuō)服力。RPD 這個(gè)指標(biāo)在近紅外領(lǐng)域幾乎是默認(rèn)要報(bào)的它回答的是「模型誤差比樣品本身的波動(dòng)小了多少」寫(xiě)論文和答辯時(shí)比單純報(bào) R2 更能堵住追問(wèn)。注意預(yù)處理里的統(tǒng)計(jì)量只能從訓(xùn)練集算出再用到測(cè)試集上。SNV 本身逐樣本獨(dú)立問(wèn)題不大但后面如果做標(biāo)準(zhǔn)化均值方差必須來(lái)自訓(xùn)練集否則就是數(shù)據(jù)泄漏指標(biāo)再好看也站不住。3. ConvNet、VitNet、DeepVit、SpectFormer四個(gè)模型家族怎么選TL 后綴到底加了什么文件列表里兩組 py 的命名邏輯很清楚ConvNet、VitNet、DeepVit、SpectFormer 是四個(gè)基礎(chǔ)結(jié)構(gòu)帶 TL 后綴的是各自對(duì)應(yīng)的遷移學(xué)習(xí)變體。TL 基本可以斷定是 Transfer Learning 的縮寫(xiě)具體遷移策略和權(quán)重來(lái)源以 README.md 為準(zhǔn)但這個(gè)命名本身已經(jīng)說(shuō)明了一份完整的對(duì)比實(shí)驗(yàn)應(yīng)該怎么組織——同一個(gè)結(jié)構(gòu)從頭訓(xùn)練 vs 遷移學(xué)習(xí)各跑一組正好回答答辯時(shí)最常被問(wèn)的「遷移學(xué)習(xí)在你的任務(wù)里到底有沒(méi)有用」。3.1 ConvNet.py一維卷積基線先把下限做扎實(shí)一維卷積是近紅外光譜回歸最樸素的深度學(xué)習(xí)方案也是所有對(duì)比實(shí)驗(yàn)的「地板」。光譜輸入從二維圖像降成一維序列后Conv1d的卷積核只在波長(zhǎng)方向上滑動(dòng)感受野覆蓋相鄰的波長(zhǎng)點(diǎn)。近紅外吸收峰不是孤立點(diǎn)而是一段連續(xù)的波段卷積核能直接學(xué)習(xí)到「峰形 局部波段相關(guān)性」這類特征。典型結(jié)構(gòu)是Conv1d → BatchNorm → ReLU → AdaptiveAvgPool1d → Linear卷積核大小取 17 左右對(duì)應(yīng)約 17 個(gè)波長(zhǎng)點(diǎn)的鄰域接近半個(gè)吸收峰的寬度。AdaptiveAvgPool1d(1)把整條光譜壓成一個(gè)向量替代全連接層處理序列長(zhǎng)度參數(shù)量小、不容易過(guò)擬合。這個(gè)結(jié)構(gòu)跑出來(lái)的結(jié)果就是整組實(shí)驗(yàn)的基準(zhǔn)線Transformer 模型如果連這個(gè)下限都打不過(guò)問(wèn)題通常不在結(jié)構(gòu)而在數(shù)據(jù)預(yù)處理或超參。3.2 VitNet.py 與 DeepVit.py把光譜切成 patch 的 Transformer 路線VitNet 的思路是把一維光譜切成長(zhǎng)度相等的 patch每個(gè) patch 經(jīng)過(guò)線性映射后加上位置編碼再送進(jìn) Transformer 編碼器。近紅外光譜里同一個(gè)成分的吸收峰會(huì)出現(xiàn)在多個(gè)波段相隔很遠(yuǎn)但相關(guān)性很強(qiáng)Transformer 的自注意力機(jī)制正好擅長(zhǎng)建模這種長(zhǎng)程依賴。比如水的 O-H 吸收在 1400 nm 和 1900 nm 附近都有信號(hào)純 CNN 要靠堆層數(shù)才能把這兩個(gè)位置的感受野連起來(lái)Transformer 一層注意力就能直接關(guān)聯(lián)。DeepVit 從命名看是 VitNet 的加深加寬版本差異大概率在 Transformer encoder 的層數(shù)depth和嵌入維度embed_dim上。這兩個(gè)模型的對(duì)比要回答的問(wèn)題是在近紅外這種中等長(zhǎng)度序列上加大模型容量能不能帶來(lái)實(shí)打?qū)嵉氖找孢€是只是把訓(xùn)練時(shí)間變長(zhǎng)。光譜數(shù)據(jù)樣本量通常只有幾百到幾千DeepVit 非常容易過(guò)擬合所以它的默認(rèn)超參、正則化設(shè)置反而是這組實(shí)驗(yàn)里最有看點(diǎn)的部分。3.3 SpectFormer.pyCNN 局部特征 Transformer 全局依賴的混合SpectFormer 從名字看是 Spect Former 的組合也是我拆這個(gè)包時(shí)最期待的文件。純 CNN 的局部感受野高效但視野窄純 Transformer 的全局注意力強(qiáng)但容易在小樣本上過(guò)擬合混合結(jié)構(gòu)是這兩條路的折中淺層先用一維卷積提取局部峰形特征再把卷積輸出的特征圖切成 patch 或直接作為 token 序列送進(jìn) Transformer。這個(gè)設(shè)計(jì)對(duì)近紅外光譜是說(shuō)得通的。卷積層輸出的每個(gè)位置已經(jīng)包含了鄰近波段的局部信息Transformer 在這些局部特征之上建??绮ǘ蔚囊蕾囌Z(yǔ)義上比直接用原始波長(zhǎng)點(diǎn)做注意力更干凈。SpectFormer 和 ConvNet 的對(duì)比實(shí)際是「局部特征 全局關(guān)系」和「純局部特征」的對(duì)比SpectFormer 和 VitNet 的對(duì)比則是「局部建模是否必要」的對(duì)比。三組模型互相參照正好覆蓋評(píng)審最關(guān)心的兩個(gè)問(wèn)題結(jié)構(gòu)設(shè)計(jì)有沒(méi)有依據(jù)、實(shí)驗(yàn)有沒(méi)有對(duì)照組。3.4 帶 TL 后綴的那一組遷移學(xué)習(xí)到底遷移了什么四個(gè) TL 文件是這套代碼包在畢設(shè)答辯時(shí)最有價(jià)值的部分。近紅外深度學(xué)習(xí)最常見(jiàn)的困境是光譜數(shù)據(jù)量太少?gòu)念^訓(xùn)練一個(gè)容量稍微大一點(diǎn)的網(wǎng)絡(luò)就過(guò)擬合。遷移學(xué)習(xí)的常規(guī)做法是先在更大規(guī)模的數(shù)據(jù)上預(yù)訓(xùn)練再在目標(biāo)任務(wù)上微調(diào)。但要注意ImageNet 預(yù)訓(xùn)練權(quán)重是二維三通道輸入近紅外光譜是一維輸入輸入層通道數(shù)和序列長(zhǎng)度都對(duì)不上直接加載預(yù)訓(xùn)練權(quán)重一定會(huì)報(bào) shape mismatch。所以這里的 TL 變體遷移的通常不是 ImageNet 權(quán)重本身而是這類結(jié)構(gòu)的「訓(xùn)練策略」——比如凍結(jié)淺層、只微調(diào)深層或者把同一結(jié)構(gòu)在公開(kāi)光譜數(shù)據(jù)上做自監(jiān)督預(yù)訓(xùn)練再微調(diào)。具體是哪種以 README.md 的實(shí)現(xiàn)為準(zhǔn)但你可以把 TL 變體理解成「同一個(gè)網(wǎng)絡(luò)換一種訓(xùn)練方式」而不是「換一個(gè)網(wǎng)絡(luò)」。這張表是我拆完文件后對(duì)每個(gè)腳本的定位判斷文件結(jié)構(gòu)路線在實(shí)驗(yàn)矩陣中的角色ConvNet.py1D CNN 基線定下限作為所有模型對(duì)比的基準(zhǔn)ConvNetTL.pyConvNet 遷移學(xué)習(xí)看預(yù)測(cè)練/微調(diào)策略對(duì) CNN 是否有提升VitNet.py光譜切 patch 進(jìn) Transformer純 Transformer 路線的基線VitNetTL.pyVitNet TL看遷移學(xué)習(xí)能否緩解 Transformer 過(guò)擬合DeepVit.py加深加寬的 VitNet驗(yàn)證模型容量在光譜數(shù)據(jù)上的上限D(zhuǎn)eepVitTL.pyDeepVit TL深度模型與遷移學(xué)習(xí)是否兼容SpectFormer.pyCNN Transformer 混合局部特征 全局依賴的混合結(jié)構(gòu)SpectFormerTL.pySpectFormer TL混合結(jié)構(gòu)結(jié)合遷移學(xué)習(xí)后的最終效果提示拆這類代碼包時(shí)最忌直接把文件名當(dāng)結(jié)論。先打開(kāi) README.md確認(rèn)每個(gè) TL 文件里預(yù)訓(xùn)練權(quán)重的來(lái)源和微調(diào)策略再?zèng)Q定你的論文里怎么寫(xiě)「遷移學(xué)習(xí)」這一章。4. 把訓(xùn)練跑起來(lái)命令行入口、六個(gè)關(guān)鍵超參與三組實(shí)驗(yàn)矩陣環(huán)境裝好后這套代碼的入口設(shè)計(jì)比較直白每個(gè)模型一個(gè) py 文件直接python 文件名.py就能啟動(dòng)訓(xùn)練。動(dòng)手深度學(xué)習(xí)的人都知道跑通一個(gè)骨架半小時(shí)排一個(gè)環(huán)境 bug 一晚上。近紅外回歸任務(wù)用 PyTorch 實(shí)現(xiàn)是當(dāng)前最常見(jiàn)的方案torch 1.13 以上版本對(duì)Conv1d和 Transformer Encoder 的支持都很成熟不需要額外裝復(fù)雜的庫(kù)。4.1 環(huán)境與入口先跑通 README.md再換你自己的數(shù)據(jù)建議環(huán)境是 Python 3.8核心依賴包括 numpy、pandas、scipy、scikit-learn、torch。命令行入口的一般形式如下實(shí)際參數(shù)名以 README.md 為準(zhǔn)但結(jié)構(gòu)基本逃不出這套# 以 SpectFormer 從頭訓(xùn)練為例 python SpectFormer.py --data ./data/spectra.csv \ --target column_name \ --epochs 80 --batch_size 32 --lr 1e-3 \ --patch_size 64 --embed_dim 256 --depth 8這個(gè)命令的邏輯是--data指定光譜 CSV 路徑--target指定目標(biāo)值列名后面四個(gè)是模型的四個(gè)核心超參。第一次運(yùn)行時(shí)先把--epochs改小到 10確認(rèn)數(shù)據(jù)能讀進(jìn)來(lái)、loss 能下降再跑完整訓(xùn)練。這么做的好處是避免數(shù)據(jù)格式問(wèn)題在跑了 30 個(gè) epoch 之后才暴露白白浪費(fèi)時(shí)間。GPU 不是必須的。近紅外光譜序列長(zhǎng)度在幾百到一千左右樣本量通常不過(guò)萬(wàn)這類規(guī)模的模型在 CPU 上跑 80 個(gè) epoch 也就是幾十分鐘到一兩個(gè)小時(shí)。如果你的數(shù)據(jù)量特別大再考慮租云平臺(tái)或者用 GPU 實(shí)例畢設(shè)場(chǎng)景完全不需要一上來(lái)就上多卡。4.2 六個(gè)關(guān)鍵超參值和含義要對(duì)得上調(diào)參之前先搞清楚每個(gè)參數(shù)在這個(gè)任務(wù)里的物理含義。這六個(gè)參數(shù)是近紅外光譜 Transformer 和 CNN 訓(xùn)練里最常動(dòng)的優(yōu)先級(jí)從高到低排列如下參數(shù)建議范圍對(duì)光譜回歸的影響batch_size16–64光譜樣本差異大batch 太小 loss 抖太大泛化下降lrCNN 1e-3Transformer 1e-4Transformer 對(duì)學(xué)習(xí)率極敏感1e-3 大概率發(fā)散epochs60–120配早停光譜任務(wù)收斂慢固定 epochs 不如早??煽縫atch_size32–128過(guò)小序列太長(zhǎng)、注意力稀疏過(guò)大丟失局部峰形embed_dim128–512決定 token 的表示容量小數(shù)據(jù)用 128–256 更穩(wěn)depth4–12層數(shù)過(guò)深在小樣本上直接過(guò)擬合4–6 層起步這里有一條血淚經(jīng)驗(yàn)Transformer 類模型在光譜上的學(xué)習(xí)率玄學(xué)比圖像還嚴(yán)重。圖像任務(wù) 1e-3 能跑近紅外 Transformer 同樣用 1e-3 經(jīng)常 loss 前 20 個(gè) epoch 紋絲不動(dòng)降到 1e-4 才開(kāi)始下降。原因是光譜序列的數(shù)值范圍和圖像像素差異很大Adam 的一階矩估計(jì)在稀疏梯度下容易走偏。所有模型先固定隨機(jī)種子再在同一套超參下跑對(duì)比否則你分不清是結(jié)構(gòu)差異還是隨機(jī)種子差異。4.3 三組實(shí)驗(yàn)矩陣從基線到上限一次跑齊這套代碼最省心的設(shè)計(jì)是它天然支持三組實(shí)驗(yàn)。第一組是四個(gè)基礎(chǔ)模型從頭訓(xùn)練對(duì)比回答「同樣預(yù)算下哪種結(jié)構(gòu)強(qiáng)」第二組是四個(gè) TL 模型對(duì)比回答「遷移學(xué)習(xí)能不能漲點(diǎn)」第三組是消融實(shí)驗(yàn)挑一個(gè)模型改 patch_size、embed_dim、depth回答「你論文里的參數(shù)為什么取這個(gè)值」。實(shí)驗(yàn)組模型要回答的問(wèn)題A 組從頭訓(xùn)練基線ConvNet、VitNet、DeepVit、SpectFormer四種結(jié)構(gòu)的默認(rèn)能力排序B 組遷移學(xué)習(xí)對(duì)比ConvNetTL、VitNetTL、DeepVitTL、SpectFormerTL預(yù)測(cè)練/微調(diào)策略在同一結(jié)構(gòu)上的增益C 組消融實(shí)驗(yàn)任選一模型改 patch_size / embed_dim / depth每個(gè)超參對(duì)結(jié)果的貢獻(xiàn)有多大實(shí)驗(yàn)矩陣的管理建議用一張 Excel 或 CSV 記錄每次運(yùn)行的參數(shù)和結(jié)果。近紅外實(shí)驗(yàn)跑一次不長(zhǎng)但架不住次數(shù)多不記錄的話三天后就分不清哪組是哪組了。每組實(shí)驗(yàn)固定同一個(gè)隨機(jī)種子這是整套實(shí)驗(yàn)可復(fù)現(xiàn)的前提。4.4 訓(xùn)練日志怎么判早停、最優(yōu)權(quán)重與 loss 曲線訓(xùn)練腳本骨架可以抽象成下面的邏輯不管包里的實(shí)現(xiàn)細(xì)節(jié)如何好的訓(xùn)練循環(huán)都應(yīng)該包含固定種子、保存最優(yōu)權(quán)重、早停三件事# 訓(xùn)練骨架固定種子、保存最優(yōu)、早停 import torch import numpy as np def set_seed(seed42): np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42) model build_model_from_args(args) # 替換成包里的 ConvNet / SpectFormer 等 optimizer torch.optim.AdamW(model.parameters(), lrargs.lr, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxargs.epochs) best_val float(inf) patience 0 for epoch in range(args.epochs): train_loss run_one_epoch(model, optimizer, train_loader, modetrain) val_loss, val_r2 run_one_epoch(model, optimizer, val_loader, modeeval) if val_loss best_val: best_val val_loss torch.save(model.state_dict(), best_checkpoint.pt) patience 0 else: patience 1 if patience 15: print(fearly stop at epoch {epoch}) break邏輯說(shuō)明set_seed(42)固定了 numpy 和 torch 的隨機(jī)源保證兩次運(yùn)行結(jié)果一致這是實(shí)驗(yàn)可復(fù)現(xiàn)的地基。AdamW比 Adam 多了權(quán)重衰減解耦在光譜小數(shù)據(jù)上更不容易過(guò)擬合weight_decay1e-4是經(jīng)驗(yàn)值。CosineAnnealingLR讓學(xué)習(xí)率隨訓(xùn)練周期余弦下降前期大步探索、后期小步收斂比固定學(xué)習(xí)率好用。判斷訓(xùn)練是否正常重點(diǎn)看兩個(gè)曲線train loss 持續(xù)下降說(shuō)明模型在學(xué)val loss 下降后反彈說(shuō)明過(guò)擬合開(kāi)始val loss 震蕩不降說(shuō)明學(xué)習(xí)率過(guò)高或 patch_size 不合適。torch.save(model.state_dict(), best_checkpoint.pt)保存的是權(quán)重而不是整個(gè)模型加載時(shí)要用model.load_state_dict(torch.load(best_checkpoint.pt))這是 PyTorch 的推薦做法遷移到新數(shù)據(jù)時(shí)也只需要換權(quán)重文件。5. 近紅外光譜深度學(xué)習(xí)避坑手冊(cè)五個(gè)翻車(chē)現(xiàn)場(chǎng)與排查思路近紅外深度學(xué)習(xí)的坑九成不在網(wǎng)絡(luò)設(shè)計(jì)而在數(shù)據(jù)口徑。這一章是這套代碼最常見(jiàn)的五個(gè)翻車(chē)場(chǎng)景每條都是我見(jiàn)過(guò)或自己踩過(guò)的真實(shí)案例按「現(xiàn)象 → 原因 → 解決」寫(xiě)清楚。5.1 隨機(jī)劃分讓 R2 虛高到 0.99換個(gè)儀器就翻車(chē)現(xiàn)象直接用train_test_split(random_state42)劃分?jǐn)?shù)據(jù)測(cè)試集 R2 到了 0.99loss 曲線漂亮得像教材里的示意圖。你以為模型已經(jīng)練成結(jié)果用到新采集的樣品上預(yù)測(cè)值和實(shí)測(cè)值能偏出半個(gè)數(shù)量級(jí)。原因近紅外樣品往往來(lái)自同一批次、同一工藝相鄰編號(hào)的光譜差異極小。隨機(jī)切分會(huì)把非常相似的樣本同時(shí)分進(jìn)訓(xùn)練和測(cè)試集模型等于變相記住了樣品編號(hào)附近的數(shù)據(jù)這是典型的數(shù)據(jù)泄漏。論文復(fù)現(xiàn)時(shí)別人用同樣的數(shù)據(jù)卻復(fù)現(xiàn)不出這個(gè) R2問(wèn)題不在代碼在劃分方式。解決正式實(shí)驗(yàn)用 Kennard-Stone 按光譜距離劃分訓(xùn)練集和測(cè)試集讓測(cè)試集覆蓋模型沒(méi)見(jiàn)過(guò)的光譜范圍。如果只是為了快速驗(yàn)證流程隨機(jī)劃分加固定種子可以但要把「隨機(jī)劃分 R2 虛高」寫(xiě)進(jìn)論文的局限性。我從那以后每次拿到近紅外數(shù)據(jù)第一件事就是問(wèn)一句這批樣本是不是同一批采集的是的話直接放棄隨機(jī)劃分。5.2 axis 選錯(cuò)把每一列波長(zhǎng)做歸一化光譜形變被抹平現(xiàn)象預(yù)處理代碼里寫(xiě)了一句X (X - X.mean(axis0)) / X.std(axis0)跑完模型 R2 和 RMSE 都還能看但換一組新數(shù)據(jù)后預(yù)測(cè)值整體偏移而且模型怎么調(diào)都救不回來(lái)。原因axis0是沿著波長(zhǎng)方向統(tǒng)計(jì)每一列做標(biāo)準(zhǔn)化這會(huì)抹掉同一樣本內(nèi)部不同波段的相對(duì)強(qiáng)弱。而近紅外吸光度最有用的信息恰恰是同一光譜內(nèi)部不同波段的相對(duì)關(guān)系——SNV 和標(biāo)準(zhǔn)正態(tài)變換都要求按樣本行計(jì)算也就是axis1。搞反方向后光譜的峰形被破壞模型學(xué)到的是被扭曲后的特征。解決先打印數(shù)組形狀確認(rèn)(N, L)預(yù)處理一律用axis1對(duì)每個(gè)樣本內(nèi)部操作。代碼里寫(xiě)上注釋標(biāo)注「按樣本行做不改方向」如果用了標(biāo)準(zhǔn)化而非 SNV均值方差必須來(lái)自訓(xùn)練集測(cè)試集復(fù)用同一組統(tǒng)計(jì)量。5.3 Transformer 訓(xùn)練抖得像心電圖loss 就是下不來(lái)現(xiàn)象VitNet 或 SpectFormer 一上來(lái) train loss 就高頻抖動(dòng)把學(xué)習(xí)率降到 1e-4 之后還在抖或者 loss 掉到一定程度就卡死不動(dòng)val loss 完全不跟。原因三個(gè)最常見(jiàn)的原因疊加——patch_size 切得太小導(dǎo)致序列過(guò)長(zhǎng)Transformer 的注意力在長(zhǎng)序列上不穩(wěn)定學(xué)習(xí)率沒(méi)有 warmup前幾個(gè) step 就把權(quán)重沖亂輸入特征沒(méi)有歸一化數(shù)值范圍過(guò)大讓注意力權(quán)重的 softmax 飽和。解決patch_size 取 64 以上近紅外幾百個(gè)波長(zhǎng)點(diǎn)切出 8–16 個(gè) patch 就夠?qū)W習(xí)率降到 1e-4 并加 warmup前 10 個(gè) epoch 線性升到目標(biāo)學(xué)習(xí)率輸入過(guò)一遍預(yù)處理保證均值接近 0。如果還抖就把 depth 從 8 降到 4減少一層注意力就少一層不穩(wěn)定因素。5.4 R2 很好看RMSE 卻大得離譜邊界樣本在作怪現(xiàn)象測(cè)試集 R2 0.96看起來(lái)很不錯(cuò)RMSEP 卻比工業(yè)標(biāo)準(zhǔn)高一大截。畫(huà)出預(yù)測(cè)-實(shí)測(cè)散點(diǎn)圖發(fā)現(xiàn)大部分點(diǎn)貼合對(duì)角線只有一兩個(gè)極端樣本的殘差特別大。原因R2 對(duì)線性擬合比例敏感RMSE 對(duì)平方誤差敏感。一兩個(gè)離群點(diǎn)能把 RMSE 拉爆但 R2 仍然好看。這是近紅外回歸報(bào)告里最常見(jiàn)的「指標(biāo)打架」只報(bào) R2 會(huì)高估模型只報(bào) RMSE 會(huì)低估模型。真實(shí)原因往往是測(cè)試集里混入了極端濃度樣本或者個(gè)別樣品表面狀態(tài)異常導(dǎo)致光譜失真。解決三個(gè)指標(biāo)一起報(bào)R2、RMSEP、RPD 缺一不可。畫(huà)出殘差圖把偏差超過(guò) 3 倍標(biāo)準(zhǔn)差的樣本單獨(dú)標(biāo)出來(lái)。如果是記錄錯(cuò)誤就修正如果是真實(shí)極端工況要把這類樣本單獨(dú)評(píng)估或者補(bǔ)充更多極端樣本進(jìn)訓(xùn)練集。論文里只報(bào) R2 不報(bào)殘差答辯時(shí)被問(wèn)住幾乎是必然的。5.5 TL 版本跑不起來(lái)一維光譜加載不了二維預(yù)訓(xùn)練權(quán)重現(xiàn)象想用 ImageNet 預(yù)訓(xùn)練權(quán)重做遷移學(xué)習(xí)加載權(quán)重時(shí)直接報(bào) shape mismatch強(qiáng)行忽略形狀錯(cuò)誤后訓(xùn)練完全不漲甚至從頭開(kāi)始 loss 也不降。原因ImageNet 預(yù)訓(xùn)練權(quán)重是二維三通道輸入近紅外光譜是一維輸入輸入層通道數(shù)和序列長(zhǎng)度都對(duì)不上。能夠匹配的只有中間層的結(jié)構(gòu)輸入層和輸出層必然報(bào)錯(cuò)。就算跳過(guò)輸入層預(yù)訓(xùn)練模型學(xué)到的二維邊緣、紋理特征對(duì)一維光譜的吸收峰也沒(méi)有什么可遷移的價(jià)值。解決打開(kāi) README.md先確認(rèn) TL 版本的預(yù)訓(xùn)練權(quán)重到底從哪來(lái)、怎么加載。如果包內(nèi)沒(méi)有一維預(yù)訓(xùn)練權(quán)重常見(jiàn)做法是把 TL 理解成「訓(xùn)練策略」而不是「直接拷權(quán)重」——比如先在公開(kāi)光譜數(shù)據(jù)集上做自監(jiān)督預(yù)訓(xùn)練再在目標(biāo)數(shù)據(jù)上微調(diào)或者凍結(jié)淺層、只微調(diào)深層。把 TL 的實(shí)現(xiàn)方式寫(xiě)清楚答辯時(shí)反而成為加分項(xiàng)。6. 把結(jié)果做成論文級(jí)圖表五折交叉驗(yàn)證、殘差分布與 RPD 報(bào)告實(shí)驗(yàn)跑完只是第一步近紅外回歸的結(jié)論必須經(jīng)過(guò)交叉驗(yàn)證才有說(shuō)服力。單次劃分的測(cè)試集 R2 只能證明「這組數(shù)據(jù)上跑了一次」五折交叉驗(yàn)證能把結(jié)論從「一次運(yùn)氣」變成「平均表現(xiàn)」。6.1 五折交叉驗(yàn)證這樣跑交叉驗(yàn)證的關(guān)鍵坑在預(yù)處理統(tǒng)計(jì)量的泄漏。每一折都必須在訓(xùn)練折上重新計(jì)算標(biāo)準(zhǔn)化參數(shù)測(cè)試折只能用訓(xùn)練折算出來(lái)的均值方差否則等于提前偷看了答案。# 五折交叉驗(yàn)證每折在訓(xùn)練折上重算預(yù)處理統(tǒng)計(jì)量防止數(shù)據(jù)泄漏 from sklearn.model_selection import KFold from sklearn.metrics import r2_score, mean_squared_error import numpy as np X np.load(X_preprocessed.npy) # 已經(jīng)做完 SGSNV 的譜圖矩陣 y np.load(y_target.npy) # 濃度/含量真值 kf KFold(n_splits5, shuffleTrue, random_state42) r2_list, rmse_list [], [] for fold, (tr_idx, te_idx) in enumerate(kf.split(X)): X_tr, X_te X[tr_idx], X[te_idx] y_tr, y_te y[tr_idx], y[te_idx] # 標(biāo)準(zhǔn)化只能在訓(xùn)練折上擬合 mu, sigma X_tr.mean(axis0), X_tr.std(axis0) 1e-8 X_tr (X_tr - mu) / sigma X_te (X_te - mu) / sigma model train_model(X_tr, y_tr) # 換成包里的 ConvNet 或 SpectFormer pred model.predict(X_te) r2_list.append(r2_score(y_te, pred)) rmse_list.append(mean_squared_error(y_te, pred, squaredFalse)) print(fR2 {np.mean(r2_list):.3f} ± {np.std(r2_list):.3f}) print(fRMSE {np.mean(rmse_list):.3f} ± {np.std(rmse_list):.3f})邏輯說(shuō)明KFold(shuffleTrue, random_state42)先打亂再分折避免樣本順序影響折的構(gòu)成。每折循環(huán)里重算mu和sigma這一步是交叉驗(yàn)證和數(shù)據(jù)泄漏的分水嶺。最后輸出的均值加減標(biāo)準(zhǔn)差比單次 R2 可信得多寫(xiě)論文時(shí)直接作為模型性能的正式指標(biāo)。6.2 回歸圖與 RPD 報(bào)告交叉驗(yàn)證之后還差兩張圖就能把結(jié)論釘死。第一張是預(yù)測(cè)值對(duì)實(shí)測(cè)值的散點(diǎn)圖對(duì)角線是理想預(yù)測(cè)線點(diǎn)越貼線越好斜率偏離 1 說(shuō)明存在系統(tǒng)性偏差。第二張是殘差圖橫軸是實(shí)測(cè)值縱軸是預(yù)測(cè)減實(shí)測(cè)殘差如果在零點(diǎn)上下均勻分布說(shuō)明模型沒(méi)有明顯的系統(tǒng)性傾向如果呈現(xiàn)漏斗形則說(shuō)明濃度越高誤差越大。RPD 的計(jì)算方式是把測(cè)試集 y 的標(biāo)準(zhǔn)差除以 RMSEP這個(gè)比值直接反映模型的實(shí)用價(jià)值。RPD 大于 3 說(shuō)明模型可以用于定量預(yù)測(cè)2.5 到 3 之間說(shuō)明模型可用于粗糙篩選小于 2 說(shuō)明在這個(gè)任務(wù)上不如直接取均值。這套結(jié)論連同交叉驗(yàn)證的均值方差一起寫(xiě)進(jìn)論文的結(jié)果表比單獨(dú)報(bào)一個(gè) R2 的說(shuō)服力強(qiáng)很多。從那以后我拿到任何近紅外數(shù)據(jù)都強(qiáng)制先跑一遍五折交叉驗(yàn)證用平均 R2 和 RPD 決定要不要繼續(xù)往下做而不是盯著單次劃分的數(shù)字自我感動(dòng)。這套代碼包的價(jià)值也正在這里——它把結(jié)構(gòu)、對(duì)比、驗(yàn)證的鏈路都鋪好了剩下要你動(dòng)手的是把數(shù)據(jù)口徑和驗(yàn)證邏輯做扎實(shí)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取