深度学习高光谱入门代码手把手指导

高光谱图像那密密麻麻的波段看着就让人头大是不是?别慌,今天咱们直接用代码撕开这层神秘面纱。先把手弄脏——加载数据才是正经事。高光谱数据通常藏在.mat文件里,咱们用scipy来拆这个快递:

from scipy.io import loadmat
import numpy as np

data = loadmat('Indian_pines_corrected.mat')['indian_pines_corrected']
gt = loadmat('Indian_pines_gt.mat')['indian_pines_gt']
print(f"数据尺寸:{data.shape},标签尺寸:{gt.shape}")

看到输出是(145,145,200)和(145,145)没?这表示200个波段的空间数据,每个像素对应一个标签。但直接处理200维要命啊,咱们先做个降维手术:

from sklearn.decomposition import PCA

h, w, c = data.shape
flatten_data = data.reshape(-1, c)
pca = PCA(n_components=30)  # 保留30个主成分
reduced_data = pca.fit_transform(flatten_data).reshape(h, w, -1)
print(f"降维后尺寸:{reduced_data.shape}")

这时候数据变成(145,145,30),舒服多了吧?但直接扔进神经网络会被维度打脸。咱们得造点小方块——把每个像素及其周围区域抠出来:

def create_patches(data, gt, window_size=15):
    half_size = window_size // 2
    padded_data = np.pad(data, [(half_size, half_size), (half_size, half_size), (0, 0)], mode='reflect')
    patches = []
    labels = []
    
    for i in range(half_size, padded_data.shape[0]-half_size):
        for j in range(half_size, padded_data.shape[1]-half_size):
            patch = padded_data[i-half_size:i+half_size+1, j-half_size:j+half_size+1]
            label = gt[i-half_size, j-half_size]
            if label != 0:  # 跳过背景类
                patches.append(patch)
                labels.append(label-1)  # 标签从0开始
    return np.array(patches), np.array(labels)

X, y = create_patches(reduced_data, gt)
print(f"样本形状:{X.shape},标签数量:{len(y)}")

看到生成的(10249, 15, 15, 30)样本没?这才是神经网络能消化的格式。上模型环节,整个轻量级的HybridSN结构:

import torch
import torch.nn as nn

class HyperSpectraNet(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        # 光谱特征提取
        self.conv3d_1 = nn.Conv3d(1, 8, (7, 3, 3)) 
        self.conv3d_2 = nn.Conv3d(8, 16, (5, 3, 3))
        # 空间特征提取  
        self.conv2d = nn.Conv2d(16*7, 64, 3)
        self.fc = nn.Linear(64*7*7, num_classes)
        
    def forward(self, x):
        x = x.unsqueeze(1)  # 增加通道维度
        # 光谱卷积 [B,1,15,15,30] -> [B,8,9,13,28]
        x = self.conv3d_1(x)
        x = nn.ReLU()(x)
        # 继续压缩光谱维度 [B,8,9,13,28] -> [B,16,5,11,26]
        x = self.conv3d_2(x)
        x = nn.ReLU()(x)
        # 合并光谱维度 [B,16,5,11,26] -> [B,16*5,11,26]
        b, c, d, h, w = x.shape
        x = x.view(b, c*d, h, w)
        # 空间卷积 [B,80,11,26] -> [B,64,9,24]
        x = self.conv2d(x)
        x = nn.ReLU()(x)
        x = x.view(b, -1)
        return self.fc(x)

model = HyperSpectraNet(num_classes=16)
print(f"参数量:{sum(p.numel() for p in model.parameters())}")  # 约50万参数

这结构妙在先用3D卷积榨取光谱特征,然后转2D卷积抓空间信息。注意维度变化:[1,15,15,30]输入,经过两次3D卷积后光谱维度从30被压缩到26,再展平进入2D卷积。

深度学习高光谱入门代码手把手指导

训练环节得注意类别不平衡问题,咱们上加权采样:

from torch.utils.data import DataLoader, WeightedRandomSampler

class_counts = np.bincount(y)
class_weights = 1. / class_counts
weights = class_weights[y]
sampler = WeightedRandomSampler(weights, len(weights))

train_loader = DataLoader(torch.utils.data.TensorDataset(
    torch.FloatTensor(X).permute(0,3,1,2),  # 调整维度为[通道, 高, 宽]
    torch.LongTensor(y)), 
    batch_size=64, sampler=sampler)

最后整个训练循环,记得用交叉熵损失:

optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
criterion = nn.CrossEntropyLoss()

for epoch in range(50):
    model.train()
    for inputs, labels in train_loader:
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
    # 验证环节
    with torch.no_grad():
        val_outputs = model(val_inputs)
        acc = (val_outputs.argmax(1) == val_labels).float().mean()
    print(f"Epoch {epoch} | Loss: {loss.item():.4f} | Acc: {acc:.2%}")

跑完50个epoch,准确率大概能到85%左右。要是想再提升,试试这些骚操作:

  1. 在数据增强里加随机旋转和镜像
  2. 把PCA换成MNF变换
  3. 在模型里加SE注意力模块
  4. 用标签平滑代替普通交叉熵

最后提醒新手:高光谱分类最大的坑是数据泄露!千万别在降维前就划分数据集,否则PCA会偷看到测试集信息。正确姿势是先分train/test,再分别做PCA拟合。

更多推荐