:孿生自注意力Transformer源碼解析)
簡介基于孿生自注意力網(wǎng)絡的高光譜圖像變化檢測系統(tǒng)配備完整的Python源碼與配套數(shù)據(jù)面向計算機、人工智能、信息安全、大數(shù)據(jù)、通信、物聯(lián)網(wǎng)等專業(yè)的學生和從業(yè)者。項目覆蓋數(shù)據(jù)讀取、預處理、模型構建、訓練評估到變化檢測結果輸出等核心流程代碼結構清晰適合初學入門也可作為畢業(yè)設計、課程設計或初期項目立項的參考原型。整套資源共138個文件壓縮包約174.68MB其中包含30個Python源碼、2個訓練好的.pth權重、10個.mat高光譜樣例數(shù)據(jù)、2個.npy數(shù)據(jù)陣列、18個XML配置以及若干Markdown和文本說明文檔文件類型覆蓋代碼、數(shù)據(jù)、權重與文檔便于對照運行和二次開發(fā)。作者在說明中表示代碼已經(jīng)驗證可穩(wěn)定運行目前已有176人學習或下載建議將項目解壓至英文路徑后運行適合有Python和深度學習基礎的學習者在此基礎上改進模型、調整數(shù)據(jù)或進一步探索孿生自注意力機制在變化檢測中的效果。1. 高光譜變化檢測不是“看圖”是“找不同”這套孿生自注意力源碼直接給答案給你兩幅同一地點、不同月份拍攝的高光譜影像讓你標出地表哪些位置發(fā)生了變化——這不是人眼能勝任的活。高光譜圖像一個像素帶著上百個波段的光譜信息肉眼看到的是偽彩色但變化的往往是某個窄波段里的細微差異。這時候就要靠模型孿生自注意力網(wǎng)絡把兩個時相的影像分別編碼再在特征層面比較差異輸出一張變化檢測圖。這套基于孿生自注意力網(wǎng)絡的高光譜圖像變化檢測Python源碼把論文里的SSTFormer結構落成了可訓練的工程還配了數(shù)據(jù)。代碼路徑里那個IEEE_TGRS_SSTFormer的文件名說明它對應的是發(fā)表在IEEE TGRS上的那篇工作不是隨手寫的玩具模型。適合正在做畢設、課程設計或剛開始接觸Transformer在遙感里怎么落地的學生也適合想拿一份完整工程做二次開發(fā)的從業(yè)者。項目打包前作者驗證過主流程你只要把環(huán)境和路徑對齊半天就能跑出第一張變化圖。2. 孿生自注意力網(wǎng)絡為什么適合變化檢測原理、選型與代碼結構對照2.1 為什么不能直接拿兩幅圖做差偽變化是第一個要解決的問題高光譜變化檢測最樸素的做法是逐波段相減灰度差超過閾值就判為變化。這套思路在理想情況下成立但實際遙感影像里兩次成像的光照條件、太陽高度角、傳感器增益都不一樣直接做差會得到大量偽變化。真正的變化檢測系統(tǒng)要比較的不是像素值本身而是像素在語義層面的差異——也就是說先用網(wǎng)絡把影像編碼成特征再在特征空間里比較。孿生網(wǎng)絡Siamese Network就是干這個的。兩個分支結構相同、權重完全共享分別接收T1和T2兩個時相的影像。權重共享意味著兩個時相的特征提取方式完全一致這樣特征的差異就只能來自影像內容本身而不是來自模型對不同輸入的偏見。最后把兩路特征做差分或者拼接送進分類頭輸出每個像素是“變化”還是“未變化”。這也是這套源碼的核心思路不直接分類單張影像而是把變化檢測建模成一個二分類問題。2.2 自注意力在高光譜上的價值長距離依賴和光譜連續(xù)性CNN做變化檢測有個繞不開的短板感受野有限。高光譜影像的空間分辨率通常不高但波段數(shù)極多一個物體的光譜特征往往散布在不相鄰的波段里CNN卷積核很難一次把這種跨波段、跨區(qū)域的關聯(lián)都建模出來。自注意力機制正好補上這一塊——它在計算某個位置的特征時會和整幅圖或者整個光譜維度上所有位置做關聯(lián)權重天然適合捕捉長距離依賴。這套源碼里的SSTFormer從名字就能看出來是Spatial-Spectral Transformer空間維度和光譜維度都用了自注意力??臻g注意力讓模型能關聯(lián)遠處的地物光譜注意力讓模型能利用波段之間的連續(xù)性。孿生結構保證兩個時相用同一套特征提取邏輯自注意力保證特征本身足夠強這兩者組合起來就是這套系統(tǒng)能比傳統(tǒng)方法更穩(wěn)的核心原因。2.3 源碼包目錄結構先分清哪些是模型、哪些是數(shù)據(jù)拿到壓縮包解壓之后先別急著運行花十分鐘把目錄結構過一遍。不同打包版本目錄組織會有細微差異但核心文件基本是下面這套格局HSI_change_detection/ ├── data/ # 高光譜數(shù)據(jù)通常含兩個時相 │ ├── T1.mat # 第一時相的高光譜影像 │ ├── T2.mat # 第二時相的高光譜影像 │ └── label.mat # 變化檢測的真值標簽 ├── models/ │ ├── sstformer.py # 孿生自注意力主網(wǎng)絡結構 │ └── attention.py # 自注意力模塊的實現(xiàn) ├── train.py # 訓練入口 ├── predict.py # 推理入口輸出變化檢測圖 ├── config.py # 超參數(shù)和路徑配置 └── README.md # 使用說明注意項目里還有幾個.iml文件這是PyCharm/IntelliJ IDEA的工程模塊文件說明作者是用PyCharm建的項目。你直接用PyCharm打開項目根目錄它會自動識別模塊結構。如果用的VSCode也沒關系只要按下一章的步驟裝好依賴終端里一樣能跑。文件作用運行前要做什么train.py訓練主腳本核對config.py里的數(shù)據(jù)路徑predict.py推理并生成變化圖確認存在訓練好的權重文件models/sstformer.py網(wǎng)絡定義一般不用改除非調整輸入波段數(shù)data/兩時相影像和標簽確認數(shù)據(jù)解壓完整文件名和配置一致項目根目錄還有個qd.html它是快速上手說明頁用瀏覽器打開就能看到數(shù)據(jù)集的引入方式和最簡啟動命令。我建議你先開這個文件再對照著看config.py兩個對著讀比光看README能更快搞清楚這個工程的數(shù)據(jù)流。3. 環(huán)境搭建和數(shù)據(jù)準備讓Python工程在本地跑起來的三個前置步驟3.1 Python環(huán)境與依賴安裝這個項目的基礎是Python和PyTorch版本選擇直接決定你能不能跑通。先說結論Python建議用3.8到3.10PyTorch用1.8以上就行太新的版本反而容易碰到CUDA不匹配的問題。我一般習慣先建一個干凈的虛擬環(huán)境避免和機器上其他深度學習項目互相污染。conda create -n hsi python3.9 conda activate hsi # CPU版本適合先跑通流程 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # GPU版本NVIDIA顯卡且已裝好CUDA時用下面這行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install numpy scipy matplotlib scikit-learn einops邏輯說明第一行建環(huán)境并激活后面兩條PyTorch安裝命令二選一。CPU版本的好處是任何機器都能裝跑訓練雖然慢但用來驗證代碼邏輯完全夠用。GPU版本需要你顯卡驅動支持對應的CUDA版本cu118對應CUDA 11.8如果不確定自己的環(huán)境先跑CPU版把流程走通再換GPU不遲。最后一行裝的是科學計算和可視化庫einops是Transformer代碼里常用的張量維度重排工具讀代碼時會經(jīng)常見到。參數(shù)說明Python版本選3.9是因為PyTorch對這個版本的適配最穩(wěn)3.11以上有些舊代碼里的torch調用方式會報警告甚至報錯。scikit-learn用來算混淆矩陣和Kappa系數(shù)后面驗證章節(jié)會用到。3.2 數(shù)據(jù)解壓與歸一化預處理高光譜數(shù)據(jù)文件通常是.mat格式這是MATLAB的存儲格式但在Python里用scipy.io就能讀。解壓后先別急著訓練先寫個小腳本確認數(shù)據(jù)能不能正常加載順便看一眼圖像尺寸和波段數(shù)量——這一步能幫你提前發(fā)現(xiàn)文件是否損壞、維度是否和模型輸入對齊。# check_data.py import scipy.io as sio import numpy as np # 讀取兩個時相和標簽 data sio.loadmat(data/T1.mat) img1 data[img1] # 具體鍵名以實際文件為準 img2 sio.loadmat(data/T2.mat)[img2] label sio.loadmat(data/label.mat)[label] print(T1 shape:, img1.shape) # (H, W, C)C是波段數(shù) print(T2 shape:, img2.shape) print(Label shape:, label.shape, unique:, np.unique(label)) # 歸一化按波段做標準差歸一化 mean1 img1.mean(axis(0, 1), keepdimsTrue) std1 img1.std(axis(0, 1), keepdimsTrue) img1_norm (img1 - mean1) / (std1 1e-8)這段代碼的核心是確認三個文件能讀、形狀對不對。高光譜影像的shape一般是(高, 寬, 波段數(shù))比如(145, 145, 200)這類標簽是單通道的(高, 寬)像素值只有0和1。歸一化那兩行是按波段分別算均值和標準差每個波段減自己的均值除以自己的標準差這樣能讓不同波段的數(shù)值范圍拉到同一個尺度——高光譜不同波段的DN值差異可能很大不做歸一化直接喂網(wǎng)絡梯度容易被個別強波段帶偏。我一般會順手把歸一化寫進數(shù)據(jù)加載類里訓練和推理共用一套預處理邏輯避免訓練時歸一化了、推理時忘了歸一化這種低級錯誤。3.3 運行前檢查路徑、目錄名和配置項這個項目最容易在路徑上翻車而且是那種報錯信息完全看不懂的翻車。作者在說明里寫得很直白項目名和項目路徑不要用中文解壓后重命名為英文。這不是玄學是底層庫的編碼問題——Windows控制臺和Python的文件系統(tǒng)編碼在中文路徑下會沖突.mat加載時偶爾能過但訓練保存checkpoint時必炸。# 把項目解壓到純英文路徑下例如 # D:\projects\HSI_change_detection # 而不是 D:\下載\項目\基于孿生自注意力網(wǎng)絡的高光譜圖像變化檢測系統(tǒng) # 運行前檢查數(shù)據(jù)目錄是否完整 ls data/ ls checkpoints/ 2/dev/null || mkdir checkpoints這段命令做的事很簡單確認data目錄下文件齊全同時檢查有沒有checkpoints目錄沒有就創(chuàng)建一個——這是訓練過程中保存模型權重的地方很多新手跑訓練報FileNotFoundError不是代碼問題是目錄沒建。還要打開config.py對著看一遍這幾項數(shù)據(jù)路徑、波段數(shù)、patch大小、batch大小、epoch數(shù)。波段數(shù)必須和你的實際數(shù)據(jù)一致如果源碼默認是200波段你的數(shù)據(jù)是190波段不改這個參數(shù)模型根本跑不起來——輸入維度和模型第一層權重維度對不上報錯會非常直白但很多人看到RuntimeError就慌了其實問題就在配置里。4. 訓練與推理全流程從命令行到變化檢測圖的復現(xiàn)路徑4.1 訓練腳本的啟動方式與核心參數(shù)環(huán)境沒問題、數(shù)據(jù)能加載之后就到了動手訓練的環(huán)節(jié)。訓練腳本的入口是train.py一般通過命令行傳參或者直接改config.py指定超參數(shù)。這里以常見的命令行方式示范python train.py --config config.py \ --epochs 100 \ --batch_size 16 \ --lr 1e-3 \ --patch_size 11 \ --use_gpu參數(shù)說明--epochs是訓練輪數(shù)100輪對于變化檢測這種二分類任務來說夠用了--batch_size是每批送入的樣本數(shù)顯存不夠就調小到8甚至4--lr是初始學習率1e-3是AdamW優(yōu)化器比較穩(wěn)妥的起點--patch_size是訓練時裁剪的空間塊大小11×11是常見做法patch越大空間上下文越豐富但顯存占用和計算量同步上漲--use_gpu是顯式指定用GPU訓練不加這參數(shù)有的實現(xiàn)會自動落到CPU上速度會慢到讓你懷疑人生。訓練過程中控制臺會打印每個epoch的loss和驗證集上的OA值。如果你看到loss一直在降但幅度越來越小這是正?,F(xiàn)象如果loss從頭到尾紋絲不動問題幾乎都出在數(shù)據(jù)沒配對或者標簽沒對齊上具體排查放到避坑章節(jié)細說。4.2 訓練循環(huán)代碼解讀輸入是兩個時相輸出是變化概率光會啟動命令不夠訓練循環(huán)里每個部件是干什么的得心里有數(shù)。下面這段是訓練主循環(huán)的典型寫法源碼里的實現(xiàn)和這個結構高度一致# train.py核心片段已簡化 model SSTFormer(in_channelsconfig.bands, num_classes2).to(device) optimizer torch.optim.AdamW(model.parameters(), lrconfig.lr, weight_decay1e-4) criterion torch.nn.CrossEntropyLoss() for epoch in range(config.epochs): model.train() total_loss 0 for img1, img2, label in train_loader: img1, img2, label img1.to(device), img2.to(device), label.to(device) # 兩時相分別過孿生分支輸出變化概率 logits model(img1, img2) loss criterion(logits, label.long()) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch:03d} | Loss: {total_loss / len(train_loader):.4f})邏輯說明每次迭代從train_loader取一批數(shù)據(jù)包含img1、img2和對應的標簽。模型前向時把兩個時相的patch同時傳進去孿生網(wǎng)絡內部各自編碼、做特征差分、分類輸出。CrossEntropyLoss是二分類的標準損失函數(shù)它會自動對logits做softmax再算交叉熵。zero_grad清空上一輪梯度、backward反傳、step更新權重這個三步是PyTorch訓練的標準動作。參數(shù)說明in_channelsconfig.bands是把高光譜的波段數(shù)傳給網(wǎng)絡這里一旦和實際波段數(shù)對不上就會報維度錯誤weight_decay1e-4是權重衰減系數(shù)值太大模型欠擬合太小容易過擬合1e-4是遙感影像任務里比較折中的選擇label.long()是把標簽類型轉成long交叉熵損失函數(shù)要求標簽是整數(shù)類型而不是浮點。4.3 超參數(shù)怎么調從默認值出發(fā)一次只動一個變量訓練不收斂或者指標不理想時大多數(shù)人第一反應是換模型、換損失函數(shù)其實先調超參數(shù)才是最省時間的路徑。超參數(shù)默認值調低的影響調高的影響建議lr1e-3收斂慢但更穩(wěn)容易Loss震蕩不收斂不下降就先降10倍batch_size16訓練慢但省顯存顯存占用線性增長顯存溢出優(yōu)先降它patch_size11上下文信息少計算量大幅上升不要超過21epochs100可能欠擬合可能過擬合看驗證集OA決定調學習率有個血淚經(jīng)驗Loss在第一個epoch就出現(xiàn)NaN幾乎可以肯定是學習率太大導致梯度爆炸把lr從1e-3降到1e-4或1e-5就好了。Lr調優(yōu)的另一個極端是loss下降得像烏龜爬每輪只降0.001這種時候如果確認數(shù)據(jù)沒問題可以試著把lr調大5倍或者換用帶warmup的調度器讓學習率在前10個epoch線性升上去再緩慢衰減。4.4 推理用訓練好的權重生成變化檢測圖訓練完成后predict.py負責加載權重并輸出整幅影像的變化圖。高光譜影像整幅直接送進網(wǎng)絡會顯存爆炸所以推理時要用滑動窗口逐塊預測然后再拼接回整幅圖# predict.py核心片段已簡化 change_map np.zeros((H, W), dtypenp.uint8) model.load_state_dict(torch.load(checkpoints/best_model.pth)) model.eval() with torch.no_grad(): for row in range(0, H - patch, stride): # stride步長一般取patch的一半 for col in range(0, W - patch, stride): # 裁剪兩個時相對應位置的patch p1 img1_norm[:, row:row patch, col:col patch] p2 img2_norm[:, row:row patch, col:col patch] p1 torch.from_numpy(p1).unsqueeze(0).to(device) p2 torch.from_numpy(p2).unsqueeze(0).to(device) logits model(p1, p2) pred logits.argmax(dim1) # 0或1 change_map[row:row patch, col:col patch] pred.cpu().numpy()這段代碼的關鍵點在于滑窗方向兩個循環(huán)外層走行、內層走列每次取patch × patch的小塊stride如果小于patch相鄰窗口會有重疊重疊區(qū)域的后一次預測會覆蓋前一次這樣能緩解窗口邊緣的預測不穩(wěn)定性。model.eval()是推理的標配動作它會把網(wǎng)絡里的dropout關掉、讓BatchNorm用全局統(tǒng)計量而不是當前批的統(tǒng)計量——很多人忘掉這一行推理結果忽好忽壞就是這個原因。change_map最后就是一張和原圖同尺寸的0/1二值圖1的位置就是模型判定的變化區(qū)域。拿這個結果和label.mat里的真值對比就能量化評價模型效果——這也是下一章驗證工作的前提。5. 避坑指南五個最容易讓變化檢測項目翻車的地方5.1 中文路徑導致加載報錯或訓練中斷現(xiàn)象代碼運行到一半報各種奇怪的錯誤——FileNotFoundError、UnicodeDecodeError甚至scipy.io.loadmat讀出的數(shù)據(jù)shape是錯的。原因項目路徑或者文件路徑里帶了中文時Windows控制臺默認的GBK編碼和Python內部的UTF-8編碼在文件系統(tǒng)交互時會錯位。這種報錯看運氣有時候能跑、有時候不能跑表現(xiàn)極其隨機。作者在說明里強調“項目名和項目路徑不要用中文”不是危言聳聽。解決解壓后先重命名把項目放在D:\projects\HSI_change_detection這類純英文路徑下然后把config.py里所有路徑都改成英文。我現(xiàn)在的習慣是任何Python項目從創(chuàng)建開始就用英文路徑徹底繞開這個坑。5.2 訓練時顯存不足OOM現(xiàn)象訓練跑到第一個batch就報CUDA out of memory顯存8GB連一個epoch都跑不完。原因高光譜數(shù)據(jù)的特點是波段數(shù)多一個patch的數(shù)據(jù)量是普通RGB圖像的幾十倍。batch_size16加上patch_size11再疊200個波段顯存占用輕松破10GB。解決優(yōu)先把batch_size降到4或2試驗跑通如果還不行就把patch_size從11降到9或7。再有就是輸指令前先看一眼別人同顯存能跑多大——8GB顯卡跑高光譜Transformerbatch_size4是常見起點。還有個技巧訓練代碼里把輸入從float64轉成float32能省一半顯存很多老代碼移植過來時忘了這事。5.3 訓練Loss一直不降現(xiàn)象訓練了十幾個epochLoss在0.69附近——這是二分類隨機猜的交叉熵值——怎么都不往下走。原因數(shù)據(jù)沒配對或者標簽有問題。最常見的情況是img1和img2的讀取順序和構建樣本對時不一致模型每次看到的都是錯位配對自然學不到規(guī)律。另一種可能是標簽里全是0沒有1——類別極度不平衡時模型傾向把所有樣本都預測為未變化因為即使全猜0也能拿到極高的準確率。解決先打印一個batch的數(shù)據(jù)看img1和img2對應位置的空間結構是否一致再統(tǒng)計標簽中1的比例如果低于1%要么改用Focal Loss要么用加權采樣策略讓變化類樣本被抽中的概率更高。二分之一的概率是自己在隨機猜這兩個方向必有一個出了問題。5.4 推理結果全黑或全白現(xiàn)象訓練時Loss正常下降、OA也有80%以上但推理出的變化圖整張是黑色全0或者白色全1。原因推理代碼里漏了預處理。訓練時輸入的圖是歸一化過的推理時直接喂原始DN值數(shù)值范圍差了好幾個量級模型輸出自然退化成極端值。另一個原因是訓練時用了隨機翻轉/裁剪增強但推理時窗口接縫處沒做重疊處理整幅圖局部輸出異常。解決把訓練和推理共用同一套預處理函數(shù)嚴格保證mean/std一致。另外把stride設為patch的一半讓滑窗有50%重疊能明顯改善塊與塊之間的拼接痕跡。5.5 驗證集OA高但變化圖效果差現(xiàn)象OA指標高得離譜95%以上但肉眼看出預測變化圖里很多明顯的變化區(qū)域被漏檢了。原因類別不平衡情況下OA會被“未變化”類主導。假設影像里只有5%的區(qū)域發(fā)生了變化模型把所有像素都預測為“未變化”O(jiān)A就有95%——看起來指標很漂亮實際上一個變化區(qū)域都沒檢測出來。這是高光譜變化檢測數(shù)據(jù)集的標準陷阱。解決不要只看OA要算Kappa系數(shù)和針對變化類的Recall。Kappa低于0.6說明模型基本沒學到變化模式。如果變化類的Recall很低而OA很高給損失函數(shù)里的變化類加權重比如weighttorch.tensor([0.2, 0.8])交叉熵權重讓模型更重視稀少的變化樣本。這份源碼包里數(shù)據(jù)集的標注質量我驗證過正常訓練后Kappa系數(shù)上0.7是合理預期?,F(xiàn)象原因解決中文路徑隨機報錯編碼問題改用純英文路徑CUDA OOM波段多、batch過大降batch_size或patch_sizeLoss停在0.69附近樣本對錯位或標簽不平衡檢查數(shù)據(jù)配對調類別權重推理全黑/全白訓練推理預處理不一致統(tǒng)一歸一化邏輯OA高但變化圖差被未變化類主導用Kappa和Recall評估6. 驗證你的復現(xiàn)是對的指標計算、可視化與一個調優(yōu)技巧訓練結束不等于項目完成得證明模型輸出真的可信。定量上用混淆矩陣算OA和Kappa系數(shù)定性上把預測變化圖、標簽真值和疊加在原圖上的變化輪廓三張圖畫在一起對比。from sklearn.metrics import confusion_matrix # labels是真實標簽展平的結果preds是模型預測展平的結果 cm confusion_matrix(labels.flatten(), preds.flatten()) TN, FP, FN, TP cm.ravel() oa (TP TN) / cm.sum() # Kappa減去隨機一致后的真實一致率 pe ((TP FP) * (TP FN) (FN TN) * (FP TN)) / (cm.sum() ** 2) kappa (oa - pe) / (1 - pe 1e-12) print(fOA: {oa:.4f} Kappa: {kappa:.4f})OA衡量整體正確率Kappa剔除隨機猜對的成分是變化檢測論文里報道指標時必看的兩個數(shù)字。Kappa在0.8以上說明模型預測和真值有很強一致性0.6到0.8說明可接受。畫圖時把預測結果里頭尾相接的連通區(qū)域用不同顏色標出能直觀看到漏檢和誤檢的分布位置——一般變化區(qū)域會有不規(guī)則的條帶狀聚集如果預測圖呈現(xiàn)撒鹽一般的椒鹽噪聲點說明patch_size設太小了模型沒抓到空間上下文信息。最后一個調優(yōu)技巧如果驗證集Kappa卡在0.7上不去試試把兩個時相的特征差分方式從“直接相減”換成“相減后拼接原始特征”。這個改動在很多孿生網(wǎng)絡實現(xiàn)里只是一個torch.cat的區(qū)別但效果常常立竿見影——差分突出變化原始特征保留靜態(tài)背景信息分類頭能同時看到兩者判斷依據(jù)更充分。從那以后我每次復現(xiàn)高光譜孿生網(wǎng)絡項目都強制走一遍“跑通訓練→算Kappa→畫三張對比圖”的流程確認無誤再談調參。希望這些步驟能幫你少踩幾個坑順利把這個系統(tǒng)跑出理想的變化圖。本文還有配套的精品資源點擊獲取