简介:卷积神经网络(CNN)通过多层卷积核自动学习像素到语义的映射,显著提升了遥感图像分析的效率与准确性。在PyTorch框架下,利用预训练的AlexNet或ResNet模型进行迁移学习,可有效应对滑坡识别中样本量小、类别不平衡等挑战。本文从CNN基础原理出发,介绍遥感滑坡识别的数据划分、数据增强、模型构建与训练调优等关键环节,并结合实际工程场景讨论滑动窗口推理与模型部署技巧。该方法可为地质灾害监测、应急救援提供快速决策支持,帮助开发者在有限样本下构建高召回率的自动识别系统。
1. 遥感滑坡识别为什么依赖CNN和PyTorch
滑坡体在遥感影像上表现为颜色、纹理、地形特征的突变,人工圈定隐患区域往往需要数小时,而强降雨后的救援窗口期只有几十个小时。用卷积神经网络自动识别滑坡,本质是把问题建模为图像二分类或者区域分割。CNN通过多层卷积核自动学习从像素到语义的映射,不需要手工设计边缘、纹理特征;PyTorch的动态计算图和灵活的Tensor API让实验迭代速度明显优于静态图框架。
这套基于PyTorch的遥感滑坡识别资源包含四部分:可运行的卷积神经网络源码(AlexNet与ResNet两种结构)、遥感影像数据集、训练好的模型参数以及数据划分脚本split_data.py。它配备的README给出了运行依赖和复现步骤,属于一个完整的课程设计或期末大作业。对于那些已经了解CNN前向传播,但还没独立组织过完整训练流程的开发者,这个项目能从数据准备一直走到推理验证,正好卡在“知识”和“实践”之间的位置。
2. 遥感数据集准备与split_data.py划分策略
2.1 滑坡影像数据的目录组织
滑坡识别项目的常见做法是按类别建立文件夹,然后用torchvision.datasets.ImageFolder读取。一个典型结构是:
dataset/ ├── train/ │ ├── landslide/ │ │ ├── slide_001.jpg │ │ └── ... │ └── non_landslide/ │ ├── normal_001.jpg │ └── ... ├── val/ └── test/ImageFolder要求同一类别的图片必须放到同一个子目录中,目录名就是类别名。这样写出来的数据加载代码最短:datasets.ImageFolder(root='dataset/train')会自动把字母序(landslide在non_landslide之前)映射为类别0和1。训练好的模型对类别顺序非常敏感,所以一旦确定顺序,后续使用模型时也要用相同的类别名称。
实际遥感滑坡数据集往往类别不平衡,负样本(非滑坡)远多于正样本。如果直接按顺序遍历目录,训练时模型会倾向于预测多数类。处理不平衡有两个思路:一是通过加权采样器,让每个batch里正负样本比例接近;二是构造数据集时人工筛选一部分难负样本。split_data.py里如果只做了随机切分,建议保留原始类别分布的同时,在数据增强中把正样本的随机裁剪比例加大。
2.2 split_data.py按比例切分训练集、验证集和测试集
项目中提供的数据划分脚本用于将原始图片随机分配到三个子集中。类似功能的脚本我常这样实现:
import os import shutil import random def split_data(source_dir, target_dir, train_ratio=0.7, val_ratio=0.15, seed=42): random.seed(seed) categories = os.listdir(source_dir) # ['landslide', 'non_landslide'] for cat in categories: cat_path = os.path.join(source_dir, cat) images = [f for f in os.listdir(cat_path) if f.endswith(('.jpg', '.png'))] random.shuffle(images) train_count = int(len(images) * train_ratio) val_count = int(len(images) * val_ratio) for part, slice_ in zip(['train', 'val', 'test'], [images[:train_count], images[train_count:train_count + val_count], images[train_count + val_count:]]): dst = os.path.join(target_dir, part, cat) os.makedirs(dst, exist_ok=True) for f in slice_: shutil.copy(os.path.join(cat_path, f), os.path.join(dst, f)) split_data('dataset/raw', 'dataset')这里seed非常重要,固定随机种子后,无论分多少次,同一个原始数据集都会得到完全相同的划分。在论文或期末报告中,这个细节能避免“训练集和测试集有重叠”这一类质疑。train_ratio与val_ratio的比例可以根据数据总量调整:数据量在几千张时,7:1.5:1.5是稳妥的;若只有几百张,验证集占比可以降到10%,或者把测试集合并到验证集中,只做交叉验证。
2.3 数据增强与DataLoader封装
遥感图像受拍摄时间、云层阴影、传感器噪声影响很大,直接归一化后输入网络会导致过拟合。我一般在transform里加入轻量级增强:
from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomVerticalFlip(p=0.5), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])RandomHorizontalFlip和RandomVerticalFlip对于遥感图像都适用,因为卫星拍摄的山区没有绝对上下方向;ColorJitter用来模拟不同大气条件下的色差。如果原图尺寸不一,Resize((224,224))直接变形,会损失宽高比。更好的做法是先短边缩放再中心裁剪,但为了方便批处理,很多项目直接Resize。当你发现验证精度低而训练精度高时,优先检查增强强度,而不是急着换网络。
DataLoader部分使用ImageFolder配合shuffle=True:
from torch.utils.data import DataLoader from torchvision import datasets train_dataset = datasets.ImageFolder('dataset/train', transform=train_transform) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4) val_dataset = datasets.ImageFolder('dataset/val', transform=val_transform) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)num_workers在Windows上默认会报错,需要改成0;Linux下可以设为4到8。batch_size的设定要和显存匹配,如果训练时出现CUDA out of memory,先减半batch_size,而不是改网络结构。
2.4 遥感图像的多波段输入问题
很多遥感高分影像有R、G、B、NIR等四个以上波段。CNN的第一个卷积层的in_channels通常写3,要接受多波段数据,需要修改网络第一层。常见做法是只保留RGB三个波段,或者对近红外波段做灰度化后与RGB拼接成4通道。对于入门级滑坡识别,直接使用RGB是足够的,因为滑坡体裸露的土壤在RGB下与周围植被有足够差异。如果你手头的原始数据集是GeoTIFF多光谱格式,可以先用GDAL库截取前三个波段另存为JPEG,这样后续代码不用动。
3. AlexNet与ResNet在PyTorch中的构建与对比
3.1 两个网络架构的选型逻辑
AlexNet在ImageNet-2012上把识别错误率从26%降到15%以上,是CNN在现代深度学习中的开山之作。它的核心贡献是ReLU激活函数和Dropout正则化,能让一个8层网络在当时的GPU上稳定训练。ResNet则通过残差连接把网络深度提升到152层,解决了深层网络的退化问题。对于遥感图像,滑坡区域尺度较小,纹理细节往往是关键判别信息,ResNet的卷积层能保留更细粒度的空间特征,因此通常比AlexNet收敛更快、精度更高。
下表从工程视角对比两种结构:
| 网络 | 深度 | 参数量 | 单张224x224推理耗时(ms) | 特点 |
|---|---|---|---|---|
| AlexNet | 8 | 约60M | 1.5 | 结构直观,容易实现,适合教学 |
| ResNet18 | 18 | 约11M | 2.3 | 参数量少,残差结构,适合小数据集 |
| ResNet50 | 50 | 约25M | 4.1 | 更强的特征表达,需要更多数据 |
实际项目中如果训练样本不足5000张,优先试ResNet18;你的项目提供的预训练模型如果是由ResNet50训练的,那么直接用即可,否则你重新训练时要根据显存选择。参数很少的模型不一定精度低,遥感图像类别间的差异明显,不需要特别大的模型容量。
3.2 从零实现AlexNet的关键卷积结构
手写AlexNet类的代码能够展示卷积层的组织方式:
import torch.nn as nn class AlexNet(nn.Module): def __init__(self, num_classes=2): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), ) self.classifier = nn.Sequential( nn.Dropout(0.5), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(inplace=True), nn.Dropout(0.5), nn.Linear(4096, 4096), nn.ReLU(inplace=True), nn.Linear(4096, num_classes), ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x第一层卷积用kernel_size=11, stride=4直接降低分辨率,相当于对224x224的输入很快缩到55x55。中间三个卷积层没有池化,因为池化会丢失位置信息。Dropout(0.5)放在全连接层之前,训练时随机丢弃一半神经元,让网络不过度依赖某一个特征。在PyTorch中,由于ReLU之后只有线性层和Dropout,我把多个卷积与激活封装成Sequential,这样model.features可以单独输出特征图,方便事后可视化和中间层提取。
3.3 使用torchvision加载ResNet预训练模型
torchvision提供可以直接使用的ResNet,强烈建议在遥感数据上使用预训练权重微调,而不是随机初始化训练。ImageNet预训练模型已经学到了边缘、纹理、形状等通用特征,这些特征迁移到遥感图像上仍然有效。
import torchvision.models as models base_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) num_features = base_model.fc.in_features base_model.fc = nn.Linear(num_features, 2) # 冻结前面层的参数,只训练最后一个模块 for name, param in base_model.named_parameters(): if name.startswith("layer4") or name.startswith("fc"): param.requires_grad = True else: param.requires_grad = False optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, base_model.parameters()), lr=1e-3)models.resnet18(weights=...)在最新版本中使用weights参数指定预训练权重,旧版用pretrained=True。修改fc层输出维度后,模型输出就是滑坡与非滑坡的logits。冻结参数时,filter(lambda p: p.requires_grad, ...)确保优化器不会为冻结参数计算梯度,节省显存也加快训练。对于数据集较大的情况,可以全部解冻并对整个模型做小学习率微调,比如lr=1e-5。
注意:如果你加载的项目提供的训练好的模型是用自定义结构保存的,也包含state_dict,那么加载方式是一样的,只要网络结构定义完全一致即可。如果torchvision里没有你需要的结构,需要从源码中导入项目自带的类。
3.4 输出层设计与类别映射
滑坡识别是二分类,所以输出层两个节点即可。训练时使用PyTorch的CrossEntropyLoss,它内部已经包含了Softmax操作,所以模型前向输出的是未归一化的logits,而不需要手动在最后一层加Softmax。推理阶段要概率,可以在logits上再做torch.softmax(dim=1)。类别0和类别1分别对应landslide和non_landslide目录名,要与split_data.py中的类别列表保持一致。
4. 训练循环、损失函数与结果验证
4.1 一个可复用的训练脚本骨架
把训练逻辑整理为函数,方便后续扩展为交叉验证或者多模型对比。下面是一个简化版但功能完整的训练流程:
import torch import torch.nn as nn from torch.optim import Adam from torch.optim.lr_scheduler import ReduceLROnPlateau def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total = 0, 0, 0 for images, labels in loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * images.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += images.size(0) return total_loss / total, correct / total def validate(model, loader, criterion, device): model.eval() total_loss, correct, total = 0, 0, 0 with torch.no_grad(): for images, labels in loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) total_loss += loss.item() * images.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += images.size(0) return total_loss / total, correct / total device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = base_model.to(device) criterion = nn.CrossEntropyLoss() optimizer = Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4) scheduler = ReduceLROnPlateau(optimizer, mode='min', patience=3, factor=0.5)model.train()和model.eval()切换BatchNorm和Dropout的工作模式,验证阶段忘记切换会导致结果异常。with torch.no_grad()让验证过程不构建计算图,大幅降低显存占用。ReduceLROnPlateau监控验证loss,连续3个epoch没有下降就把学习率减半,这对避免训练后期振荡很有用。
4.2 超参数设置的经验表
根据滑坡图片的大小和样本量,常用的训练参数如下:
| 超参数 | 建议范围 | 说明 |
|---|---|---|
| 输入尺寸 | 224x224 | 与ImageNet预训练模型一致 |
| batch_size | 16~64 | 受显存限制,滑坡正样本少时用小batch |
| 初始学习率 | 1e-4~1e-3 | 使用预训练模型时建议低一些 |
| 优化器 | Adam / SGD+momentum | Adam收敛快,SGD+动量最终精度略高 |
| epochs | 30~50 | 数据量大时可配合早停 |
| 类别权重 | 根据比例设weight | 正负样本比 > 8:1 时必须处理 |
设置CrossEntropyLoss的weight参数是应对类别不平衡最直接的方法。比如负样本数量是正样本的10倍,则给正样本的权重设为10,负样本设为1。代码写作:criterion = nn.CrossEntropyLoss(weight=torch.tensor([10.0, 1.0]))。注意weight的顺序要和类别索引一致,这又要求类别映射事先固定。
4.3 混淆矩阵与评估指标
仅用准确率评价滑坡识别不充分,因为如果非滑坡占95%,全预测为非滑坡也有95%准确率。要计算精确率、召回率和F1,可以用sklearn的混淆矩阵:
from sklearn.metrics import confusion_matrix, classification_report all_preds = [] all_labels = [] with torch.no_grad(): for images, labels in val_loader: images = images.to(device) outputs = model(images) all_preds.extend(outputs.argmax(1).cpu().numpy()) all_labels.extend(labels.numpy()) cm = confusion_matrix(all_labels, all_preds) print(cm) print(classification_report(all_labels, all_preds, target_names=['landslide', 'non_landslide']))在滑坡识别的实际业务中,漏报一个滑坡点的代价远高于误报,所以应重点关注召回率。如果模型对正样本的召回率低于0.6,说明抑制假阴性的能力不足,此时可以降低分类阈值,不一定要用0.5作为默认判定边界。
4.4 从训练日志判断模型状态
假设训练10个epoch,日志如下:
Epoch 1/10 train_loss:0.6931 train_acc:0.51 val_loss:0.6890 val_acc:0.55 Epoch 3/10 train_loss:0.5102 train_acc:0.72 val_loss:0.4801 val_acc:0.75 Epoch 5/10 train_loss:0.2844 train_acc:0.88 val_loss:0.2617 val_acc:0.87 Epoch 8/10 train_loss:0.1021 train_acc:0.96 val_loss:0.2055 val_acc:0.90 Epoch 10/10 train_loss:0.0613 train_acc:0.99 val_loss:0.2331 val_acc:0.89训练loss持续下降而验证loss在第8个epoch后开始上升,说明模型进入过拟合。此时应记录第7个epoch的模型参数作为最终结果,或者增强数据扩充。对于遥感小数据集,AlexNet参数量大,过拟合比ResNet更早出现,因此需要设置更强的Dropout或加入正则化。项目中如果自带训练好的模型,大概率作者已经做过早停,直接使用它比从头训练省事得多。
5. 利用训练好的模型做遥感影像滑坡预测与工程技巧
5.1 加载模型参数完成单张图像推理
项目的models目录或checkpoints目录下会存有.pth文件。加载时先定义一个与训练时完全相同的模型对象,再调用load_state_dict。
import torch from PIL import Image from torchvision import transforms from models.resnet import resnet18 # 假设项目源码里有这个类 model = resnet18(num_classes=2) state = torch.load('best_model.pth', map_location='cpu') model.load_state_dict(state['model_state_dict'] if 'model_state_dict' in state else state) model.eval() transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) img = Image.open('test_slide.jpg').convert('RGB') input_tensor = transform(img).unsqueeze(0) with torch.no_grad(): logits = model(input_tensor) prob = torch.softmax(logits, dim=1) pred = logits.argmax(dim=1).item() print(f'预测类别: {pred}, 滑坡概率: {prob[0][pred].item():.4f}')map_location='cpu'让代码在没有GPU的机器上也能运行。注意model.eval()必须在推理前调用,否则BatchNorm会使用batch统计量导致预测不稳定。如果加载后报size mismatch,通常是因为类别数不同,检查num_classes是否与本项目的二分类一致。
5.2 滑动窗口处理大尺寸遥感影像
遥感影像通常有几千乘几千像素,直接resize成224会丢失滑坡体的小尺度纹理。常见做法是使用滑动窗口将大图切分成多个小块,分别预测,再拼接成分割图或热力图。
crop_size = 224 step = 112 overlay = Image.new('L', (img_width, img_height), 0) for y in range(0, img_height - crop_size + 1, step): for x in range(0, img_width - crop_size + 1, step): crop = img.crop((x, y, x + crop_size, y + crop_size)) crop_tensor = transform(crop).unsqueeze(0) with torch.no_grad(): prob = torch.softmax(model(crop_tensor), dim=1)[0, 1].item() for px in range(x, x + crop_size): for py in range(y, y + crop_size): overlay.putpixel((px, py), int(prob * 255))窗口步长step取crop_size的一半,让相邻窗口有重叠,这样能避免滑坡体恰好被窗口边界切断。整幅图每个像素的预测值取窗口内叠加的最大或平均概率,最终形成的灰度图用阈值0.5二值化就是滑坡区域。这个方法的计算量和图像像素数成正比,想要加速可以用F.conv2d结合矩阵化,但作为课程设计,循环已经足够。
5.3 针对滑坡小样本的微调技巧
如果你的训练样本不足,而手头又有预训练模型的权重,就不要再从零训练。加载预训练权重后,把最后一层修改成二分类,先冻结前面所有层训练10个epoch,再解冻layer3和layer4训练20个epoch。解冻阶段使用lr=1e-5,因为高层特征针对遥感数据需要小步快跑。另一个技巧是随机擦除数据增强transforms.RandomErasing(),让模型不依赖某一个局部特征,对滑坡体被植被遮挡的情况非常有效。
5.4 验证下载的源码是否完整可运行
拿到源码后不要立即在命令行执行python train.py,先检查文件是否齐全。用Windows系统打开项目根目录的README.md,查看是否有环境专属的依赖版本要求。在命令行运行python split_data.py,观察生成的dataset/train里是否出现对应文件夹。如果缺失,检查路径是否带中文,PyTorch在Windows下对中文路径兼容性差。最后用python predict.py test.png跑通一次推理,输出正常后再修改参数训练自己的模型。整个流程验证无误后,再对自己的遥感影像做预测。
注意:本资源仅供学习交流,不要用于商业项目。下载使用前,最好重新确认数据集来源的许可协议。若只是课程设计展示,尽量使用作者提供的模型参数,避免因重新训练时间过长而错过答辩。
本文还有配套的精品资源,点击获取