拓冰建站拓冰建站
首页 / 资讯中心 / 正文

遥感影像场景分类中的迁移学习实战:从数据预处理到模型微调

简介基于TensorFlow迁移学习的遥感影像场景分类资源以天宫数据大赛遥感影像为真实样例面向具备Python与深度学习基础的研究者演示如何利用预训练inception-resnet-v2骨干网络完成场景识别建模。压缩包共2000个文件、约696.94MB文件类型以jpg影像为主辅以py训练/测试脚本、tfrecord数据文件、ckpt预训练模型及csv结果文件其中tfrecord用于训练与验证、zip为比赛测试数据集完整覆盖从原始影像生成tfrecord、模型微调训练到测试评估的流程。已有278人学习适合希望快速上手迁移学习、参加遥感分类比赛或复现经典分类模型的开发者。包内提供create_tfrecord.py数据预处理脚本与train_RsImage.py训练代码同时附有预赛测试集、骨干网络初始权重和结果csv可直接对照实验并根据天宫数据特点进一步调整超参与网络结构以提升精度。整体目录结构清晰便于按流程复现。1. 遥感影像场景分类为什么非得用迁移学习场景分类和语义分割不一样它给的不是逐像素标签而是给一整块影像区域贴一个语义类别——“这是居民区”“这是农田”“这是港口”。遥感影像的场景分类在实际项目中往往是地物普查、国土监测、城市规划的前置步骤做的是“先把图分对再谈细粒度解译”。模型选型上TensorFlow 是最不容易踩坑的一条路生态成熟、部署链路短、TFRecord 数据流水线现成社区里能直接复用的预训练权重也最全。但这个任务有一个绕不开的现实约束遥感影像标注极贵。一张场景级影像的标注看似只需要一个类别标签但为了保证模型泛化一个类别往往要几千上万个样本而且不同区域、不同季节、不同传感器的影像分布差异极大。从头训练一个 ResNet 级别的深度模型在常见硬件上要跑几天效果还不一定比得上在 ImageNet 上预训练过的模型做微调。迁移学习的核心逻辑就是把模型在自然图像上学到的边缘、纹理、形状等底层特征搬到遥感影像上再在少量遥感数据上微调高层语义。适合读这篇的人是已经能用 Python 处理栅格数据、但还没系统跑通一遍“预训练模型 遥感场景分类”完整流程的工程师。下面直接进入数据侧先把影像处理好再谈模型。2. 遥感影像预处理与训练集构建2.1 用 Rasterio 读取并重采样多波段影像遥感影像和普通照片的第一道坎在于格式与波段。常见的高分二号、Sentinel-2、Landsat 影像通常是多波段 GeoTIFF有的还带 16 bit 辐射分辨率。我们不能像读 JPG 一样直接把它喂给 TensorFlow。import rasterio import numpy as np with rasterio.open(scene_001.tif) as src: profile src.profile img src.read() # 形状: (C, H, W)C是波段数 transform src.transform print(波段数:, img.shape[0]) print(尺寸:, img.shape[1:]) print(数据类型:, img.dtype) print(仿射变换参数:, transform)这段代码的用途是确认影像元数据。C 维顺序在前是 rasterio 的既定格式转成 TensorFlow 需要的(H, W, C)布局时用np.transpose(img, (1, 2, 0))即可。这里有一个隐藏问题如果影像带有地理参考transform不为空而我们做的场景分类本质上是“按图块识别”不涉及像素坐标对齐那么在做滑动窗口裁剪前先决定要不要保留地理参考。我的建议是训练阶段直接丢弃地理信息只保留像素矩阵推理阶段再做结果与地理范围的对应训练时留着只有坏处——数据格式多一层复杂度没有任何收益。2.2 处理 16 bit 影像归一化不是简单除以 255遥感影像经常是 16 bit像素取值范围在 0 到 65535 之间。直接把这样的数据送进在 8 bit 图像上预训练的模型分布完全不匹配收敛会很慢甚至不收敛。常见的处理路线有两种一是百分位截断拉伸二是按统计量做标准化。推荐前者因为它对光照差异和传感器增益更鲁棒。def normalize_16bit(img, lower_percent2, upper_percent98): # img形状: (H, W, C)float32 bands img.shape[-1] out np.zeros_like(img, dtypenp.float32) for b in range(bands): band img[:, :, b] lo np.percentile(band, lower_percent) hi np.percentile(band, upper_percent) band np.clip(band, lo, hi) out[:, :, b] (band - lo) / (hi - lo 1e-6) return out这里的关键参数是lower_percent和upper_percent。遥感影像的黑边、云影、高亮地物会拉高全局最大值直接做 min-max 归一化会让绝大多数像素挤在低值区间。2% 到 98% 的截断是比较稳的默认值但如果影像里水域占比很大可以把下界放宽到 0.5%。逐波段独立归一化的原因是不同波段对地物反射率差异很大全局统一拉伸会削弱近红外波段在植被区分的贡献。2.3 滑动窗口裁剪Patch与标签文件生成场景分类的训练单元不是整幅影像而是切出来的图块Patch。图块太小会丢失场景上下文图块太大则会造成类别标签不纯。拿常见的高分二号影像分辨率约 1 米举例224x224的图块对应地面约 224 米见方对大尺度场景识别足够了如果是无人机影像分辨率约 0.05 米建议用512x512。def extract_patches(img, patch_size224, stride224): h, w, c img.shape patches [] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patch img[y:ypatch_size, x:xpatch_size, :] patches.append(patch) return np.stack(patches)stride是滑动步长。stride224表示无重叠裁剪每张 224x224 的图块都是独立的训练样本但会丢失目标恰好落在窗口边界的情况实践中如果标注掩膜如耕地地块边界比较碎可以用stride112做 50% 重叠裁剪能有效扩充样本量。这种重叠采样带来的类别不平衡问题通过下采样多数类或调整损失权重来解决后面小节会展开。2.4 TFRecord 封装与数据加载流水线TensorFlow 在数据侧的原生高性能格式是 TFRecord。把切好的图块和标签写进 TFRecord训练时用tf.data.Dataset读取省去每次迭代都做文件 IO 和裁剪的损耗。import tensorflow as tf def _bytes_feature(value): return tf.train.Feature(bytes_listtf.train.BytesList(value[value])) def _int64_feature(value): return tf.train.Feature(int64_listtf.train.Int64List(value[value])) def write_tfrecord(patches, labels, filename): with tf.io.TFRecordWriter(filename) as writer: for p, lab in zip(patches, labels): feature { image: _bytes_feature(p.tobytes()), label: _int64_feature(int(lab)) } example tf.train.Example(featurestf.train.Features(featurefeature)) writer.write(example.SerializeToString())读取时注意两点一是要按影像的原始精度还原 tensor 形状二是map函数里做归一化后会破坏Dataset的自动并行优化需要用num_parallel_calls显式指定并行度。TFRecord 并不是遥感的唯一选择样本量在几万张以内时直接读文件路径 tf.keras.preprocessing.image.load_img也完全可行TFRecord 的优势主要体现在大规模重复训练时的随机读取性能上。3. 迁移学习模型选型与预训练权重加载3.1 直推式与归纳式迁移学习遥感选哪种迁移学习方向上有一个容易被忽略的区分直推式迁移学习Transductive Transfer Learning指源域和目标域任务相同、数据分布不同比如在 ImageNet 上训练好的分类模型用到遥感场景分类上归纳式迁移学习Inductive Transfer Learning则是源域和目标域任务本身不同。遥感场景分类属于典型的直推式场景因为任务都是图像分类只是图像分布从自然影像换成了遥感影像因此可以直接复用预训练网络的特征提取层替换最后的分类层即可不需要复杂的领域自适应损失。这个区分对实操的意义在于如果你的瓶颈不是标注量而是数据分布差异极大——比如用光学影像预训练模型去分类 SAR 影像——那就不是简单微调能解决的需要考虑域适应模块。多数项目场景不会走到这一步但先有这个判断能避免浪费几周时间试错。3.2 VGG16、ResNet50、EfficientNet 的取舍TensorFlow 内置的tf.keras.applications模块提供了多个预训练模型。遥感场景分类中最常用的三个选择模型参数量遥感场景分类表现适合情况VGG161.38 亿特征提取器较浅泛化一般数据集极小纯特征提取不微调ResNet502550 万在 UC Merced、AID 等公开数据集上表现稳通用选择时间/效果均衡EfficientNet-B31200 万精度上限最高但训练收敛慢数据量超过每个类别 2000 张我的默认选择是 ResNet50。它在 ImageNet 上学到的 middle-level 特征边缘、角点、纹理分布与遥感影像的地物边界、纹理模式有不错的迁移性而且参数量让它在单卡 GPU 上也能跑得动微调。VGG16 只有在 GPU 显存极大、需要做非常激进的数据增强时才值得考虑——它的卷积层全是 3x3 堆叠感受野有限对大面积均质地物如连片农田的表征能力天然弱于 ResNet 系列。3.3 TensorFlow 加载预训练权重的两种方式TensorFlow 下加载 ImageNet 预训练权重有两种路径区别在于是否需要把权重文件单独下载。第一种是直接用tf.keras.applicationsbase_model tf.keras.applications.ResNet50( include_topFalse, weightsimagenet, input_shape(224, 224, 3), poolingavg )include_topFalse表示去掉 ImageNet 上的一千类全连接层只保留卷积部分。poolingavg把最后一层特征图做全局平均池化直接得到一维特征向量接一个 Dense 分类头即可。第二种是手动加载权重文件到自定义网络结构适合需要改卷积层步长或输入尺寸的场景。此时需要注意TensorFlow 预训练权重对输入尺寸有一定限制ResNet50 的最小输入边长是 32权重文件里第一层卷积的 kernel 尺寸是 7x7输入尺寸改了网络结构就变了不能直接 load。提示weightsimagenet首次运行时自动从网络下载权重国内网络环境建议提前手动下载后放在~/.keras/models/下文件名要和代码里的哈希值一致。3.4 冻结策略冻结多少层、BN 层怎么处理迁移学习的核心操作是冻结freeze参数。在一个典型的 ResNet50 上合理的策略不是全冻结也不是全微调而是分阶段base_model.trainable True # 冻结前 100 层只微调后面部分 for layer in base_model.layers[:100]: layer.trainable False model tf.keras.Sequential([ base_model, tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(num_classes, activationsoftmax) ])确定冻结边界时一个经验是遥感影像与自然图像的底层特征边缘、颜色块、简单纹理是共享的深层特征部件、场景结构差异大。ResNet50 有五个 Stageconv1 ~ conv5冻结到 Stage3 之前让 Stage4 和 Stage5 参与微调是一个数据量在几千张级别时的稳妥选择。如果只有几百张图冻结到 Stage4 之前。BN 层Batch Normalization在冻结时有个坑如果整个 backbone 被冻结而只训练后面的分类头BN 层会因为处于trainableFalse状态而不更新其滑动均值与方差而推理时使用的正是这些滑动统计量。如果训练数据和预训练数据的分布差异较大冻结的 BN 层会造成训练和推理之间的指标不一致。一个稳妥的做法是当训练数据量足够时只冻结卷积核参数让 BN 层保持可训练for layer in base_model.layers: if isinstance(layer, tf.keras.layers.BatchNormalization): layer.trainable True4. 模型训练从冻结特征到全局微调4.1 数据增强遥感影像需要什么样的增强遥感影像的增强和自然图像有一个明显区别不能随便做裁剪缩放。原因在于遥感影像的地物尺度是有物理意义的——一个 224x224 的图块在 1 米分辨率影像上代表 224x224 平方米的真实地表如果你随机裁一块 160x160 再缩放到 224真实地物的尺度语义就被破坏了。这会导致模型学到“错误的地物大小关系”。实用的增强策略有两个大方向。一是几何变换包括水平翻转、垂直翻转、随机旋转 90 度的整数倍不能是任意角度遥感地物有方向性任意旋转会破坏建筑物朝向信息、小幅度平移不超过图块尺寸的 10%。二是色彩变换包括亮度抖动、对比度抖动、少量高斯噪声这些模拟不同光照条件和传感器噪声。data_augmentation tf.keras.Sequential([ tf.keras.layers.RandomFlip(horizontal_and_vertical), tf.keras.layers.RandomRotation(0.25), tf.keras.layers.RandomBrightness(0.15), ])这里的RandomRotation(0.25)表示旋转范围是 [-0.25, 0.25] 弧度大约 ±14 度。如果对方向敏感的类别停车场、港口占比高可以降到 0.1。RandomBrightness(0.15)的语义是亮度增减幅度不超过原有值的 15%超过这个值会让夜间和白天影像的差异过大模型学不到稳定的光谱特征。4.2 损失函数、优化器与动态学习率遥感场景分类的单标签多分类问题损失函数没有太多悬念用SparseCategoricalCrossentropy标签是整数索引不做 one-hot或者CategoricalCrossentropy标签是 one-hot。但类别不平衡在遥感场景里几乎是常态——农田和森林覆盖的地块数量远多于港口和篮球场。应对类别不平衡第一优先级不是换损失函数而是在Dataset里做样本重采样让每个 batch 里类别的比例基本均匀。如果重采样后仍有偏向再在损失函数里加类别权重class_weights { 0: 1.0, # 农田样本多权重低 1: 2.5, # 港口样本少权重高 2: 3.0, # 稀疏住宅样本少且易混淆 } model.compile( optimizertf.keras.optimizers.AdamW(learning_rate1e-4), losstf.keras.losses.SparseCategoricalCrossentropy(from_logitsFalse), metrics[accuracy] ) model.fit( train_ds, validation_dataval_ds, epochs20, class_weightclass_weights, )优化器的选择上冻结阶段用 Adam 系列没有问题但进入微调阶段后权重更新会触及预训练参数建议加上权重衰减weight decay。AdamW把权重衰减和动量解耦正则效果比 L2 更好learning_rate1e-4是冻结特征阶段的标准起步值全局微调时降到 1e-5 量级否则 Adam 的自适应学习率会在预训练权重上产生太大的更新步长。4.3 两阶段训练先训分类头再微调特征层直接上来就全局微调大概率会遇到两个问题一是分类头随机初始化前向传播的梯度很大会把预训练特征层的参数冲乱二是 BN 层的统计量在刚开始时会剧烈变化loss 曲线出现抖动。标准做法是把训练拆成两个阶段。第一阶段冻结全部 backbone只训练新加的全连接层跑 5~10 个 epoch让随机初始化的分类头先学到从特征向量到类别映射的基本方向。base_model.trainable False model tf.keras.Sequential([ base_model, tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(num_classes, activationsoftmax) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losstf.keras.losses.SparseCategoricalCrossentropy(), metrics[accuracy] ) history1 model.fit( train_ds, validation_dataval_ds, epochs8, class_weightclass_weights, )第一阶段结束的标志不是准确率最高而是验证集的 loss 不再明显下降。此时保存权重进入第二阶段解冻部分 backbone降低学习率继续训练。model.load_weights(stage1_weights.h5) base_model.trainable True for layer in base_model.layers[:100]: layer.trainable False model.compile( optimizertf.keras.optimizers.AdamW(learning_rate1e-5, weight_decay1e-4), losstf.keras.losses.SparseCategoricalCrossentropy(), metrics[accuracy] ) callbacks [ tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6 ), tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) ] history2 model.fit( train_ds, validation_dataval_ds, epochs20, class_weightclass_weights, callbackscallbacks )第二阶段有两点值得留意。ReduceLROnPlateau的factor0.5让学习率在验证 loss 连续 3 个 epoch 不降时减半这个衰减速度对微调阶段比较温和。EarlyStopping设restore_best_weightsTrue很关键否则在最后一轮保存的模型未必是验证集上最好的而微调阶段后面几轮往往会出现轻微过拟合。4.4 回调与模型保存的最佳实践在训练过程中定期保存模型权重的代码里tf.keras.callbacks.ModelCheckpoint要设置save_best_onlyTrue并指定监控指标。这里有一个经常被忽视的细节监控val_accuracy还是val_loss在类别不平衡的场景里val_accuracy可能被多数类主导一个把所有样本都猜成多数类的模型也能拿到很高的 accuracy。如果用了类别权重建议监控val_loss因为它更能反映模型在少数类上的表现。checkpoint tf.keras.callbacks.ModelCheckpoint( best_model.keras, monitorval_loss, save_best_onlyTrue, save_weights_onlyFalse )TensorFlow 2.3 之后的 Keras 3 格式.keras会把模型结构和权重一起保存在推理时不需要重新构建模型结构直接tf.keras.models.load_model加载。如果你需要和 TensorFlow Serving 集成做在线推理推荐在训练完成后额外导出一份 SavedModel 格式。4.5 训练中的常见失败模式与排查训练过程中最典型的异常是三类。第一类是 loss 不下降且准确率停在随机水平。排查顺序先看数据增强后的图像是否还能看出地物tf.keras.preprocessing.image.save_img直接导出增强结果再看标签有没有错位——切 Patch 和写标签的循环如果索引错位会静默学错。第二类是训练集准确率高99%但验证集只有六成。这几乎一定是过拟合而不是代码 bug。对策按顺序优先尝试增强 Dropout 比率到 0.6、加大数据增强强度、降低第二阶段全连接层的参数量用全局平均池化替代 Flatten Dense 的大矩阵。第三类是验证 loss 一开始就比训练 loss 低且不下降。这常发生在使用了RandomFlip但训练集和验证集空间分布本身有差异时或者评估时忘记设置model.evaluate()而用了训练时的validation_data参数且没关闭增强——Model.fit的validation_data默认不做数据增强但如果验证集自己定义了tf.data.Dataset且套用了增强层就会出现不一致。5. 用 Grad-CAM 和 t-SNE 验证迁移特征是否真的学到地物语义模型的准确率达到预期之后不能直接交付。一个在遥感场景分类里特别重要的问题是模型到底是学到了地物的物理特征还是学到了数据集的背景伪影——比如把所有带道路的图块都分成“居民区”所有带蓝色色块的都分成“水域”。验证这个问题的标准工具是 Grad-CAM它对最后一个卷积层的特征图求类别激活热力图直接可视化模型“看哪里”。5.1 Grad-CAM 实现与影像类别激活验证import numpy as np import tensorflow as tf import matplotlib.pyplot as plt def grad_cam(model, img_tensor, class_idx, layer_nameconv5_block3_out): # img_tensor: (1, 224, 224, 3) 且已归一化 grad_model tf.keras.models.Model( inputsmodel.input, outputs[model.get_layer(layer_name).output, model.output] ) with tf.GradientTape() as tape: conv_output, predictions grad_model(img_tensor) loss predictions[:, class_idx] grads tape.gradient(loss, conv_output) pooled_grads tf.reduce_mean(grads, axis(1, 2)) conv_output conv_output[0] pooled_grads pooled_grads[0] for i in range(pooled_grads.shape[-1]): conv_output[:, :, i] * pooled_grads[i] heatmap tf.reduce_mean(conv_output, axis-1).numpy() heatmap np.maximum(heatmap, 0) heatmap / (np.max(heatmap) 1e-6) return heatmap使用这个函数时的验证思路挑出验证集里预测正确但置信度不高的样本叠加 Grad-CAM 热力图观察。一个合格的遥感场景分类模型对“港口”这类类别的高激活区域应该落在码头岸线、堆场和集装箱区域而不是影像角落的空地或水面。layer_name要依据具体模型查model.summary()确认ResNet50 的最后卷积层是conv5_block3_outEfficientNet 则是top_activation。5.2 t-SNE 嵌入检验类别可分性Grad-CAM 解释单张图的决策依据t-SNE 则验证整个特征空间的类别可分性。取全连接层之前的输出在poolingavg时是 2048 维向量对所有验证集样本提取特征用 t-SNE 降到二维后按标签着色。from sklearn.manifold import TSNE feature_extractor tf.keras.models.Model( inputsmodel.input, outputsmodel.get_layer(avg_pool).output ) features feature_extractor.predict(val_ds) tsne TSNE(n_components2, perplexity30, random_state42) features_2d tsne.fit_transform(features)perplexity30是 t-SNE 的默认值但当验证集样本量少于 100 时需要降到 5~15否则局部结构会被过度压缩。观察 t-SNE 图时注意两个重点一是同类别样本是否聚团二是不同类别之间是否有明显的分隔带。如果某个类别散成多个不相连的簇比如“森林”分成了深色簇和浅色簇说明模型在暗部森林和亮部森林上学到了不一致的表征可能需要在数据增强里加入对比度扰动。5.3 成果落地的实用格式建议TF SavedModel 与 ArcGIS 联动验证完成后进入交付环节。遥感场景分类的结果要落到 GIS 软件里做后续分析常见做法有两种一种是把分类结果写成带地理参考的 GeoTIFF另一种是生成矢量面Shapefile。第二种在 ArcMap 或 ArcGIS Pro 里更方便做地类图斑的编辑。先做推理阶段的地理参考回填import rasterio from rasterio.transform import from_origin # 假设原影像 origin 在左上角像素尺寸 1m transform from_origin( origin_x, origin_y, 1.0, 1.0 ) with rasterio.open( classification_output.tif, w, driverGTiff, heightlabels_map.shape[0], widthlabels_map.shape[1], count1, dtypeuint8, crsEPSG:4326, transformtransform, ) as dst: dst.write(labels_map, 1)labels_map是逐 Patch 的类别预测结果拼回整幅影像后的数组类别值直接用0, 1, 2...的整数编码ArcGIS 的符号系统里按类别值做唯一值渲染即可。用from_origin的前提是原始影像没有旋转和裁剪只有标准的左上角对齐如果原始影像的transform包含旋转参数直接用src.transform传给输出文件更安全。在 ArcGIS Pro 中加载这个 GeoTIFF 后可以用 Raster to Polygon 工具转成矢量图斑图层再做平滑和最小图斑面积过滤。这一步属于常见 GIS 操作核心思路就一句话分类结果必须回填地理参考否则对业务人员没有任何价值。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门