實(shí)戰(zhàn):從數(shù)據(jù)預(yù)處理到深度學(xué)習(xí)模型構(gòu)建與調(diào)優(yōu))
1. 項(xiàng)目概述從“看見(jiàn)”到“看懂”的飛躍高光譜分類(lèi)聽(tīng)起來(lái)是個(gè)挺學(xué)術(shù)的詞但說(shuō)白了它就是讓機(jī)器像經(jīng)驗(yàn)豐富的專(zhuān)家一樣不僅能“看見(jiàn)”物體更能“看懂”物體到底是什么。我們?nèi)搜劭吹降氖澜缡腔诩t、綠、藍(lán)三原色混合而成的“真彩色”圖像。但自然界中每種物質(zhì)都有其獨(dú)特的光譜“指紋”——它們對(duì)不同波長(zhǎng)的光有著不同的吸收和反射特性。高光譜成像技術(shù)就是用一個(gè)能同時(shí)捕捉數(shù)百個(gè)連續(xù)、狹窄光譜波段的“超級(jí)相機(jī)”為每個(gè)像素點(diǎn)記錄下一條完整的光譜曲線(xiàn)。這個(gè)項(xiàng)目要做的“高光譜分類(lèi)”核心任務(wù)就是基于這些海量的光譜數(shù)據(jù)自動(dòng)、準(zhǔn)確地將圖像中的每個(gè)像素點(diǎn)劃分到預(yù)設(shè)的類(lèi)別中去比如區(qū)分農(nóng)田里的玉米、小麥和水稻識(shí)別城市地物中的建筑、道路和植被或者在遙感監(jiān)測(cè)中圈定污染區(qū)域、評(píng)估作物健康狀況。它解決的痛點(diǎn)非常明確傳統(tǒng)RGB圖像或?qū)挷ǘ芜b感信息量有限在“同物異譜”同一種東西在不同條件下光譜不同和“異物同譜”不同東西光譜看起來(lái)相似面前常常力不從心。而高光譜數(shù)據(jù)憑借其豐富的光譜維度為精準(zhǔn)識(shí)別提供了可能。無(wú)論你是遙感、農(nóng)業(yè)、環(huán)境監(jiān)測(cè)領(lǐng)域的研究者還是對(duì)計(jì)算機(jī)視覺(jué)和機(jī)器學(xué)習(xí)感興趣的開(kāi)發(fā)者這個(gè)項(xiàng)目都是一個(gè)絕佳的切入點(diǎn)。它融合了數(shù)據(jù)處理、特征工程和模型算法能讓你深刻理解如何從高維數(shù)據(jù)中挖掘價(jià)值。接下來(lái)我會(huì)結(jié)合我處理過(guò)的多個(gè)實(shí)際數(shù)據(jù)集的經(jīng)驗(yàn)拆解從數(shù)據(jù)準(zhǔn)備到模型上線(xiàn)的完整流程分享那些在論文和教科書(shū)里不會(huì)寫(xiě)的實(shí)操細(xì)節(jié)和避坑指南。2. 核心思路與方案選型為什么是“光譜特征深度學(xué)習(xí)”面對(duì)高光譜數(shù)據(jù)最直接的挑戰(zhàn)就是其“維度災(zāi)難”。一個(gè)典型的高光譜影像可能包含200-300個(gè)波段但空間分辨率下的像素?cái)?shù)量動(dòng)輒數(shù)十萬(wàn)。這意味著數(shù)據(jù)維度極高但標(biāo)記樣本已知道類(lèi)別的像素往往非常稀缺且獲取成本高。因此整個(gè)項(xiàng)目的設(shè)計(jì)思路必須圍繞“如何從有限樣本中高效提取最具判別性的特征”展開(kāi)。早期的傳統(tǒng)方法如支持向量機(jī)SVM和隨機(jī)森林RF直接對(duì)光譜向量進(jìn)行分類(lèi)雖然簡(jiǎn)單但難以捕捉光譜間的復(fù)雜非線(xiàn)性關(guān)系及空間上下文信息。而深度學(xué)習(xí)尤其是卷積神經(jīng)網(wǎng)絡(luò)CNN因其強(qiáng)大的特征學(xué)習(xí)能力已成為當(dāng)前的主流方案。但具體用哪種網(wǎng)絡(luò)結(jié)構(gòu)需要權(quán)衡。方案一基于純光譜的1D-CNN。這是最直接的思路把每個(gè)像素的光譜曲線(xiàn)視為一個(gè)一維信號(hào)用一維卷積核在光譜維度上進(jìn)行特征提取。它的優(yōu)點(diǎn)是模型相對(duì)輕量訓(xùn)練快特別適合樣本量極少的情況。但缺點(diǎn)是完全忽略了像素之間的空間關(guān)系分類(lèi)結(jié)果圖可能會(huì)顯得“椒鹽噪聲”很重即同一個(gè)均質(zhì)區(qū)域內(nèi)分類(lèi)結(jié)果跳變嚴(yán)重。方案二基于空間-光譜聯(lián)合的2D/3D-CNN。這是目前效果最好的主流方案。2D-CNN如簡(jiǎn)單的VGG或ResNet變體將高光譜數(shù)據(jù)的每個(gè)波段當(dāng)作一個(gè)通道輸入一個(gè)空間塊例如以目標(biāo)像素為中心的7x7或9x9窗口同時(shí)學(xué)習(xí)空間紋理和光譜特征。3D-CNN則更進(jìn)一步使用三維卷積核同時(shí)在空間和光譜維度上滑動(dòng)能更自然地耦合空譜信息但計(jì)算量和參數(shù)量會(huì)急劇增加。方案三混合模型與注意力機(jī)制。這是前沿探索方向。例如用2D-CNN提取空間特征同時(shí)用一個(gè)并行分支如1D-CNN或Transformer提取光譜特征最后融合。或者引入注意力機(jī)制讓模型自動(dòng)關(guān)注那些判別性強(qiáng)的波段和空間區(qū)域。這類(lèi)模型性能潛力大但對(duì)數(shù)據(jù)量、調(diào)參技巧和計(jì)算資源要求更高。我的選型心得對(duì)于大多數(shù)入門(mén)和實(shí)際應(yīng)用項(xiàng)目我強(qiáng)烈建議從“基于空間塊的2D-CNN”開(kāi)始。它是一個(gè)絕佳的平衡點(diǎn)效果顯著優(yōu)于純光譜方法實(shí)現(xiàn)和理解難度低于3D-CNN且有很多成熟的代碼框架可以借鑒。在數(shù)據(jù)預(yù)處理階段通過(guò)提取空間塊我們實(shí)際上已經(jīng)將空間信息引入了模型。后續(xù)的優(yōu)化可以在此基礎(chǔ)上進(jìn)行比如引入殘差連接、注意力模塊等。確定了主干網(wǎng)絡(luò)還需要配套一系列關(guān)鍵設(shè)計(jì)如何劃分訓(xùn)練集、驗(yàn)證集和測(cè)試集如何解決樣本不平衡問(wèn)題如何設(shè)計(jì)數(shù)據(jù)增強(qiáng)策略來(lái)彌補(bǔ)樣本不足這些決策共同構(gòu)成了項(xiàng)目的技術(shù)骨架。3. 數(shù)據(jù)預(yù)處理比模型本身更重要的基石拿到高光譜數(shù)據(jù)通常是.mat或.tif格式直接丟給模型訓(xùn)練十有八九會(huì)失敗。預(yù)處理環(huán)節(jié)決定了數(shù)據(jù)質(zhì)量的上限也直接影響模型的性能下限。這個(gè)過(guò)程可以細(xì)化為幾個(gè)關(guān)鍵步驟。3.1 數(shù)據(jù)讀取與初步探查首先你需要了解你的數(shù)據(jù)。使用Python的scipy.io讀取.mat文件或用rasterio、GDAL讀取.tif文件。關(guān)鍵要弄清楚三個(gè)維度(height, width, bands)即圖像的高、寬和波段數(shù)。同時(shí)你需要對(duì)應(yīng)的標(biāo)簽圖(height, width)其中每個(gè)像素值代表類(lèi)別索引如0代表背景1代表類(lèi)別A等。import scipy.io as sio import numpy as np # 示例讀取Indian Pines數(shù)據(jù)集 data sio.loadmat(Indian_pines_corrected.mat) img data[indian_pines_corrected] # 形狀例如 (145, 145, 200) gt sio.loadmat(Indian_pines_gt.mat)[indian_pines_gt] # 形狀 (145, 145) print(f圖像形狀: {img.shape}, 標(biāo)簽形狀: {gt.shape}) print(f唯一標(biāo)簽值: {np.unique(gt)}) # 查看有哪些類(lèi)別緊接著繪制幾個(gè)典型波段或假彩色合成圖例如用第30、20、10波段作為R、G、B直觀(guān)感受數(shù)據(jù)并隨機(jī)選取幾個(gè)像素點(diǎn)繪制其光譜曲線(xiàn)觀(guān)察不同類(lèi)別的曲線(xiàn)差異。這一步能幫你建立對(duì)數(shù)據(jù)的直覺(jué)。3.2 光譜預(yù)處理與降維高光譜數(shù)據(jù)通常包含噪聲和冗余。相鄰波段間高度相關(guān)直接使用所有波段不僅計(jì)算負(fù)擔(dān)重還可能引入噪聲導(dǎo)致模型過(guò)擬合。壞波段剔除檢查并剔除信噪比極低或充滿(mǎn)噪聲的波段通常是邊緣波段。標(biāo)準(zhǔn)化/歸一化這是必須的一步。由于傳感器響應(yīng)和光照條件差異不同波段甚至不同像素的數(shù)值范圍可能差異巨大。我常用標(biāo)準(zhǔn)差標(biāo)準(zhǔn)化即對(duì)每個(gè)波段單獨(dú)處理減去均值除以標(biāo)準(zhǔn)差。這能加速模型收斂并提高性能。from sklearn.preprocessing import StandardScaler # 將3D數(shù)據(jù)重塑為2D (像素?cái)?shù), 波段數(shù)) 以便處理 h, w, b img.shape img_2d img.reshape(-1, b) scaler StandardScaler() img_2d_scaled scaler.fit_transform(img_2d) img_scaled img_2d_scaled.reshape(h, w, b)降維可選但推薦對(duì)于波段數(shù)非常多100的數(shù)據(jù)可以考慮使用主成分分析PCA將數(shù)據(jù)降到較低維度如30-50個(gè)主成分。這能大幅減少計(jì)算量且前幾個(gè)主成分往往包含了絕大部分信息。但要注意降維可能會(huì)損失一些細(xì)微的光譜特征對(duì)于依賴(lài)特定窄波段診斷的應(yīng)用需謹(jǐn)慎。3.3 樣本提取與空間塊構(gòu)建這是2D-CNN輸入準(zhǔn)備的核心。我們不是處理單個(gè)像素而是以每個(gè)有標(biāo)簽的像素為中心裁剪出一個(gè)固定大小的空間鄰域塊如7x7, 9x9, 11x11。這個(gè)塊的大小是關(guān)鍵超參數(shù)太小則空間信息不足太大則可能包含過(guò)多異質(zhì)信息且增加計(jì)算量。def extract_patches(img, gt, patch_size9, ignore_label0): 從圖像和標(biāo)簽圖中提取以有標(biāo)簽像素為中心的空間塊。 img: 預(yù)處理后的高光譜圖像 (H, W, C) gt: 地面真值標(biāo)簽圖 (H, W) patch_size: 空間塊大小奇數(shù) ignore_label: 需要忽略的標(biāo)簽如背景 half_size patch_size // 2 # 填充圖像邊緣使得邊緣像素也能提取完整塊 img_padded np.pad(img, ((half_size, half_size), (half_size, half_size), (0, 0)), modereflect) patches, labels [], [] # 獲取所有非忽略標(biāo)簽的像素坐標(biāo) valid_positions np.argwhere(gt ! ignore_label) for (y, x) in valid_positions: # 在填充后的圖像中計(jì)算中心位置 center_y, center_x y half_size, x half_size # 提取塊 patch img_padded[center_y-half_size:center_yhalf_size1, center_x-half_size:center_xhalf_size1, :] patches.append(patch) labels.append(gt[y, x]) return np.array(patches), np.array(labels) - 1 # 標(biāo)簽通常從0開(kāi)始編號(hào)關(guān)鍵注意事項(xiàng)填充方式我推薦使用reflect反射填充而非constant常數(shù)填充。反射填充能更好地保持圖像邊緣的連續(xù)性避免引入突兀的邊界值這對(duì)卷積操作更友好。提取出的patches形狀將是(N, patch_size, patch_size, bands)labels形狀是(N,)。這里的N是所有有效標(biāo)簽像素的數(shù)量。3.4 數(shù)據(jù)集劃分與樣本平衡這是最容易出問(wèn)題的一步。絕對(duì)不要用sklearn的train_test_split對(duì)像素索引進(jìn)行隨機(jī)劃分因?yàn)橄噜徬袼卦诳臻g上是高度相關(guān)的隨機(jī)劃分會(huì)導(dǎo)致訓(xùn)練集和測(cè)試集的數(shù)據(jù)分布高度相似即“數(shù)據(jù)泄露”你會(huì)得到一個(gè)虛高的、不可信的準(zhǔn)確率。正確的做法是按空間區(qū)域或按類(lèi)別比例分層采樣。一種穩(wěn)健的方法是將整個(gè)圖像劃分為若干不重疊的大網(wǎng)格如4x4。隨機(jī)選擇其中幾個(gè)網(wǎng)格的像素作為測(cè)試集其余網(wǎng)格的像素作為訓(xùn)練集和驗(yàn)證集。這樣可以確保測(cè)試集來(lái)自空間上獨(dú)立的區(qū)域評(píng)估結(jié)果更可靠。此外高光譜數(shù)據(jù)中各類(lèi)別的像素?cái)?shù)往往極不平衡。例如背景或某一主導(dǎo)地物可能占絕大多數(shù)。直接訓(xùn)練會(huì)導(dǎo)致模型偏向大類(lèi)別。解決方法包括過(guò)采樣復(fù)制少數(shù)類(lèi)別的樣本。欠采樣隨機(jī)丟棄多數(shù)類(lèi)別的部分樣本。類(lèi)別權(quán)重在損失函數(shù)中為少數(shù)類(lèi)別賦予更高的權(quán)重。這是我最常用的方法在PyTorch或TensorFlow中很容易實(shí)現(xiàn)。4. 模型構(gòu)建與訓(xùn)練實(shí)戰(zhàn)以HybridSN網(wǎng)絡(luò)為例理論說(shuō)再多不如一行代碼。這里我以一個(gè)效果和復(fù)雜度都比較均衡的混合光譜卷積網(wǎng)絡(luò)HybridSN的簡(jiǎn)化版為例展示如何用PyTorch搭建和訓(xùn)練一個(gè)高光譜分類(lèi)模型。這個(gè)網(wǎng)絡(luò)結(jié)合了3D卷積、2D卷積和全連接層能有效提取空譜特征。4.1 模型定義import torch import torch.nn as nn import torch.nn.functional as F class HybridSN(nn.Module): def __init__(self, num_bands, num_classes, patch_size9): super(HybridSN, self).__init__() # 第一層3D卷積在光譜維度上進(jìn)行壓縮 self.conv3d_1 nn.Conv3d(1, 8, kernel_size(7, 3, 3), padding(0, 1, 1)) # 計(jì)算經(jīng)過(guò)第一層3D卷積后的光譜維度 self.out_bands_3d num_bands - 7 1 # kernel_size[0]7, padding[0]0, stride默認(rèn)為1 self.conv3d_2 nn.Conv3d(8, 16, kernel_size(5, 3, 3), padding(0, 1, 1)) self.out_bands_3d self.out_bands_3d - 5 1 # 第二層3D卷積后將光譜維度壓扁轉(zhuǎn)為2D卷積 self.conv2d_1 nn.Conv2d(16 * self.out_bands_3d, 32, kernel_size3, padding1) # 計(jì)算經(jīng)過(guò)3D卷積后的空間尺寸 self.spatial_size_after_3d patch_size - 2*2 # 兩次3D卷積每次空間上padding1kernel3尺寸不變 self.conv2d_2 nn.Conv2d(32, 64, kernel_size3, padding1) self.conv2d_3 nn.Conv2d(64, 128, kernel_size3, padding1) # 全局平均池化層 self.gap nn.AdaptiveAvgPool2d(1) # 全連接層 self.fc1 nn.Linear(128, 256) self.dropout nn.Dropout(p0.4) # 加入Dropout防止過(guò)擬合 self.fc2 nn.Linear(256, num_classes) def forward(self, x): # 輸入x形狀: (batch_size, 1, bands, patch_size, patch_size) x F.relu(self.conv3d_1(x)) x F.relu(self.conv3d_2(x)) # 重塑: (batch, channels_3d * remaining_bands, height, width) batch, c, b, h, w x.shape x x.view(batch, c * b, h, w) x F.relu(self.conv2d_1(x)) x F.relu(self.conv2d_2(x)) x F.relu(self.conv2d_3(x)) x self.gap(x) # (batch, 128, 1, 1) x x.view(batch, -1) # (batch, 128) x F.relu(self.fc1(x)) x self.dropout(x) x self.fc2(x) return x4.2 訓(xùn)練流程與關(guān)鍵技巧定義了模型結(jié)構(gòu)接下來(lái)是訓(xùn)練循環(huán)。這里有幾個(gè)超參數(shù)和技巧需要特別注意。import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset from sklearn.model_selection import train_test_split # 假設(shè)我們已經(jīng)有了 patches 和 labels并已劃分為 X_train, X_val, y_train, y_val # 1. 轉(zhuǎn)換為T(mén)ensor并創(chuàng)建DataLoader train_dataset TensorDataset(torch.FloatTensor(X_train).unsqueeze(1), torch.LongTensor(y_train)) val_dataset TensorDataset(torch.FloatTensor(X_val).unsqueeze(1), torch.LongTensor(y_train)) # 注意輸入需要增加一個(gè)通道維因?yàn)镃onv3d期望輸入為 (N, C, D, H, W)我們C1 train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) val_loader DataLoader(val_dataset, batch_size128, shuffleFalse) # 2. 初始化模型、損失函數(shù)、優(yōu)化器 device torch.device(cuda if torch.cuda.is_available() else cpu) model HybridSN(num_bandsX_train.shape[-1], num_classeslen(np.unique(y_train)), patch_size9).to(device) # 使用帶權(quán)重的交叉熵?fù)p失解決類(lèi)別不平衡 class_counts np.bincount(y_train) class_weights 1. / class_counts class_weights torch.FloatTensor(class_weights).to(device) criterion nn.CrossEntropyLoss(weightclass_weights) # 優(yōu)化器選擇Adam并配合學(xué)習(xí)率衰減 optimizer optim.Adam(model.parameters(), lr0.001, weight_decay1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size20, gamma0.5) # 每20個(gè)epoch學(xué)習(xí)率減半 # 3. 訓(xùn)練循環(huán) num_epochs 100 best_val_acc 0.0 for epoch in range(num_epochs): model.train() running_loss 0.0 for inputs, targets in train_loader: inputs, targets inputs.to(device), targets.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, targets) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) epoch_loss running_loss / len(train_loader.dataset) scheduler.step() # 驗(yàn)證階段 model.eval() correct, total 0, 0 with torch.no_grad(): for inputs, targets in val_loader: inputs, targets inputs.to(device), targets.to(device) outputs model(inputs) _, predicted torch.max(outputs.data, 1) total targets.size(0) correct (predicted targets).sum().item() val_acc 100 * correct / total print(fEpoch [{epoch1}/{num_epochs}], Loss: {epoch_loss:.4f}, Val Acc: {val_acc:.2f}%) # 保存最佳模型 if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_hybridsn_model.pth)訓(xùn)練心得學(xué)習(xí)率與衰減高光譜數(shù)據(jù)訓(xùn)練容易過(guò)擬合。初始學(xué)習(xí)率不宜過(guò)大如0.001并配合StepLR或ReduceLROnPlateau當(dāng)驗(yàn)證集指標(biāo)停滯時(shí)自動(dòng)降低學(xué)習(xí)率使用這是穩(wěn)定訓(xùn)練、找到更好局部最優(yōu)解的關(guān)鍵。權(quán)重衰減Adam優(yōu)化器中的weight_decay參數(shù)即L2正則化非常重要能有效抑制模型復(fù)雜度防止過(guò)擬合。通常設(shè)置在1e-4到1e-5之間。早停上述代碼只保存了最佳模型在實(shí)際中最好加入“早?!睓C(jī)制。如果連續(xù)多個(gè)epoch驗(yàn)證集準(zhǔn)確率不再提升則停止訓(xùn)練避免無(wú)效計(jì)算和過(guò)擬合。Batch Size在GPU內(nèi)存允許的情況下可以適當(dāng)增大batch_size如64128這有助于訓(xùn)練更穩(wěn)定。但對(duì)于小樣本數(shù)據(jù)集過(guò)大的batch size可能會(huì)降低模型泛化能力。5. 結(jié)果評(píng)估與可視化超越整體準(zhǔn)確率模型訓(xùn)練完成后在獨(dú)立的測(cè)試集上評(píng)估性能。千萬(wàn)不要只用“整體準(zhǔn)確率”這一個(gè)指標(biāo)因?yàn)槿绻麛?shù)據(jù)極度不平衡即使模型把所有樣本都預(yù)測(cè)為最大類(lèi)也能獲得很高的整體準(zhǔn)確率但這毫無(wú)意義。必須使用更細(xì)致的評(píng)估指標(biāo)混淆矩陣直觀(guān)展示每個(gè)類(lèi)別被分對(duì)和分錯(cuò)的情況。每類(lèi)準(zhǔn)確率每個(gè)類(lèi)別自身的分類(lèi)精度。平均準(zhǔn)確率所有類(lèi)別準(zhǔn)確率的平均值更關(guān)注小類(lèi)別。Kappa系數(shù)考慮了隨機(jī)分類(lèi)的準(zhǔn)確率比單純準(zhǔn)確率更可靠。from sklearn.metrics import confusion_matrix, classification_report, cohen_kappa_score import seaborn as sns import matplotlib.pyplot as plt # 在測(cè)試集上預(yù)測(cè) model.eval() all_preds, all_labels [], [] with torch.no_grad(): for inputs, targets in test_loader: # 需要事先創(chuàng)建test_loader inputs, targets inputs.to(device), targets.to(device) outputs model(inputs) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(targets.cpu().numpy()) all_preds np.array(all_preds) all_labels np.array(all_labels) # 1. 計(jì)算整體指標(biāo) overall_acc np.mean(all_preds all_labels) kappa cohen_kappa_score(all_labels, all_preds) print(f整體準(zhǔn)確率: {overall_acc:.4f}) print(fKappa系數(shù): {kappa:.4f}) # 2. 分類(lèi)報(bào)告包含每類(lèi)精度、召回率、F1-score print(classification_report(all_labels, all_preds, target_namesclass_names)) # 3. 繪制混淆矩陣 cm confusion_matrix(all_labels, all_preds) plt.figure(figsize(10,8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(預(yù)測(cè)標(biāo)簽) plt.ylabel(真實(shí)標(biāo)簽) plt.title(混淆矩陣) plt.tight_layout() plt.show()可視化分類(lèi)圖是最終成果的直觀(guān)體現(xiàn)。你需要將整幅高光譜圖像的每個(gè)像素都用訓(xùn)練好的模型預(yù)測(cè)一遍生成一張彩色分類(lèi)圖并與真實(shí)地面真值圖進(jìn)行對(duì)比。def predict_entire_image(model, img_scaled, patch_size, device): 預(yù)測(cè)整幅圖像 half_size patch_size // 2 h, w, b img_scaled.shape img_padded np.pad(img_scaled, ((half_size, half_size), (half_size, half_size), (0, 0)), modereflect) # 為每個(gè)像素提取塊并預(yù)測(cè)效率較低可優(yōu)化為批量預(yù)測(cè) output_map np.zeros((h, w), dtypenp.uint8) model.eval() with torch.no_grad(): # 更高效的方式將整個(gè)滑動(dòng)窗口過(guò)程向量化或分塊批量預(yù)測(cè)這里為清晰起見(jiàn)用循環(huán) # 實(shí)際建議將圖像分割成網(wǎng)格對(duì)每個(gè)網(wǎng)格內(nèi)的所有像素塊一起預(yù)測(cè) for i in range(h): for j in range(w): patch img_padded[i:ipatch_size, j:jpatch_size, :] patch_tensor torch.FloatTensor(patch).unsqueeze(0).unsqueeze(0).to(device) # (1,1,b,p,p) output model(patch_tensor) pred torch.argmax(output, dim1).item() output_map[i, j] pred return output_map # 生成并顯示分類(lèi)圖 prediction_map predict_entire_image(model, img_scaled, patch_size9, devicedevice) plt.figure(figsize(15,5)) plt.subplot(1,3,1) plt.imshow(img_scaled[:,:,[30,20,10]]) # 假彩色顯示 plt.title(原始圖像假彩色) plt.subplot(1,3,2) plt.imshow(gt, cmapjet) plt.title(真實(shí)地物標(biāo)簽) plt.subplot(1,3,3) plt.imshow(prediction_map, cmapjet) plt.title(模型預(yù)測(cè)結(jié)果) plt.show()6. 常見(jiàn)問(wèn)題排查與性能調(diào)優(yōu)實(shí)錄在實(shí)際操作中你幾乎一定會(huì)遇到下面這些問(wèn)題。我把我的排查經(jīng)驗(yàn)和解決方案記錄下來(lái)希望能幫你節(jié)省大量時(shí)間。6.1 模型過(guò)擬合嚴(yán)重訓(xùn)練集準(zhǔn)確率高驗(yàn)證集上不去這是高光譜小樣本分類(lèi)中最常見(jiàn)的問(wèn)題?,F(xiàn)象訓(xùn)練loss持續(xù)下降訓(xùn)練acc很快接近100%但驗(yàn)證集loss在下降后很快開(kāi)始上升驗(yàn)證acc停滯不前。原因模型復(fù)雜度過(guò)高記住了訓(xùn)練樣本的噪聲而非一般規(guī)律。解決方案增強(qiáng)正則化增大weight_decay在卷積層后增加Dropout層如nn.Dropout2d使用更激進(jìn)的Dropout率0.5甚至更高。數(shù)據(jù)增強(qiáng)這是對(duì)付過(guò)擬合的利器。對(duì)空間塊進(jìn)行隨機(jī)水平/垂直翻轉(zhuǎn)、旋轉(zhuǎn)、添加微小的高斯噪聲等。注意光譜維度一般不做增強(qiáng)以免破壞其物理意義。簡(jiǎn)化模型減少網(wǎng)絡(luò)層數(shù)或卷積核數(shù)量。對(duì)于很小的數(shù)據(jù)集如Indian Pines一個(gè)簡(jiǎn)單的3層2D-CNN可能比復(fù)雜的HybridSN更有效。早停嚴(yán)格監(jiān)控驗(yàn)證集損失一旦連續(xù)5-10個(gè)epoch不降反升立即停止訓(xùn)練。獲取更多數(shù)據(jù)如果可能這是根本解決方法?;蛘呃冒氡O(jiān)督、自監(jiān)督學(xué)習(xí)利用無(wú)標(biāo)簽數(shù)據(jù)。6.2 某些類(lèi)別識(shí)別精度始終極低現(xiàn)象從混淆矩陣看某幾個(gè)類(lèi)別的生產(chǎn)者精度漏分誤差高或用戶(hù)精度錯(cuò)分誤差高非常低。原因樣本量太少或該類(lèi)光譜特征與其他類(lèi)太相似。解決方案檢查樣本數(shù)量如果該類(lèi)別訓(xùn)練樣本少于100個(gè)考慮過(guò)采樣或使用更強(qiáng)大的數(shù)據(jù)增強(qiáng)專(zhuān)門(mén)針對(duì)該類(lèi)。分析光譜曲線(xiàn)繪制該類(lèi)與易混淆類(lèi)別的平均光譜曲線(xiàn)看是否在關(guān)鍵波段有重疊。如果光譜本身難以區(qū)分可能需要引入空間上下文特征增大patch_size或外部知識(shí)。調(diào)整損失函數(shù)權(quán)重進(jìn)一步增大該類(lèi)在CrossEntropyLoss中的權(quán)重。集成學(xué)習(xí)訓(xùn)練多個(gè)模型如不同初始化、不同patch_size對(duì)難以區(qū)分的類(lèi)別進(jìn)行投票可能提升魯棒性。6.3 訓(xùn)練過(guò)程不穩(wěn)定Loss出現(xiàn)NaN或劇烈震蕩現(xiàn)象訓(xùn)練幾個(gè)batch后loss變成NaN或者loss值上下跳動(dòng)很大。原因?qū)W習(xí)率過(guò)高數(shù)據(jù)未標(biāo)準(zhǔn)化梯度爆炸。解決方案檢查數(shù)據(jù)確認(rèn)數(shù)據(jù)標(biāo)準(zhǔn)化是否已做。輸入數(shù)據(jù)中是否存在異常值NaN或inf。降低學(xué)習(xí)率將初始學(xué)習(xí)率降低一個(gè)數(shù)量級(jí)如從0.001降到0.0001。梯度裁剪在optimizer.step()之前添加torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)防止梯度爆炸。使用更穩(wěn)定的優(yōu)化器可以嘗試從Adam換為SGD with momentum雖然收斂可能慢但有時(shí)更穩(wěn)定。6.4 預(yù)測(cè)整圖速度太慢現(xiàn)象使用predict_entire_image中的雙重循環(huán)預(yù)測(cè)一張圖需要數(shù)小時(shí)。原因每個(gè)像素單獨(dú)提取塊、單獨(dú)預(yù)測(cè)效率極低。優(yōu)化方案批量預(yù)測(cè)將整張圖劃分成若干個(gè)網(wǎng)格每個(gè)網(wǎng)格內(nèi)所有像素的塊組成一個(gè)大batch一次性送入模型預(yù)測(cè)。這需要仔細(xì)處理邊緣填充和索引映射。使用滑動(dòng)窗口的卷積實(shí)現(xiàn)這需要將模型改造為全卷積網(wǎng)絡(luò)FCN一次前向傳播即可得到整張圖的分類(lèi)結(jié)果。這是最高效的方式但需要對(duì)網(wǎng)絡(luò)結(jié)構(gòu)進(jìn)行修改將最后的全連接層替換為1x1卷積。工程優(yōu)化使用torch.no_grad()和model.eval()確保數(shù)據(jù)在GPU上使用更大的batch_size進(jìn)行預(yù)測(cè)。6.5 不同數(shù)據(jù)集上泛化能力差現(xiàn)象在一個(gè)數(shù)據(jù)集上訓(xùn)練好的模型直接用到另一個(gè)數(shù)據(jù)集上效果暴跌。原因不同傳感器、不同時(shí)間、不同地點(diǎn)獲取的高光譜數(shù)據(jù)存在分布差異。解決方案域適應(yīng)這是前沿研究方向使用特定算法減小源域訓(xùn)練數(shù)據(jù)和目標(biāo)域新數(shù)據(jù)之間的分布差異。標(biāo)準(zhǔn)化確保對(duì)新數(shù)據(jù)采用與訓(xùn)練數(shù)據(jù)完全相同的預(yù)處理流程使用訓(xùn)練數(shù)據(jù)計(jì)算得到的均值和標(biāo)準(zhǔn)差進(jìn)行標(biāo)準(zhǔn)化。微調(diào)如果新數(shù)據(jù)有少量標(biāo)注可以在預(yù)訓(xùn)練模型的基礎(chǔ)上進(jìn)行微調(diào)。使用更具泛化性的特征在設(shè)計(jì)模型時(shí)考慮使用光譜導(dǎo)數(shù)、光譜指數(shù)等物理意義更明確的特征而非完全依賴(lài)數(shù)據(jù)驅(qū)動(dòng)的深度特征。高光譜分類(lèi)是一個(gè)從數(shù)據(jù)理解、預(yù)處理、模型設(shè)計(jì)到調(diào)優(yōu)的完整閉環(huán)。每個(gè)環(huán)節(jié)都需要耐心和細(xì)致的調(diào)試。我個(gè)人的體會(huì)是成功的關(guān)鍵往往不在于使用最炫酷的模型而在于對(duì)數(shù)據(jù)的深刻理解和扎實(shí)的工程實(shí)現(xiàn)。先從經(jīng)典的2D-CNN加精心預(yù)處理的基礎(chǔ)流程跑通確保評(píng)估方式正確再逐步嘗試更復(fù)雜的模型和技巧這樣建立的認(rèn)知才是最牢固的。最后別忘了保存好你的預(yù)處理參數(shù)、模型配置和訓(xùn)練日志這些都是寶貴的實(shí)驗(yàn)資產(chǎn)。