简介:这份资源面向卫星遥感、海洋学与地球物理领域的科研人员及技术开发者,围绕国产风云三号E星(FY-3E)搭载的GNOS-II仪器,复现基于星载GNSS-R的海面高度反演模型。内容融合传统物理模型与机器学习方法,采用随机森林和卷积神经网络,对比评估北斗与GPS反射信号的反演性能:BDS物理模型最大MAE约3.0m,优于GPS的约5.0m,而机器学习使两者均降至约0.4m,验证了FY-3E数据的有效性。资源包为1个PDF文件,约889KB,内含数据预处理、物理模型实现、机器学习训练与评估的完整代码及解释,并涉及数据质量控制、误差分析、空间分布可视化与系统性能对比。已有124人学习,适合希望掌握国产卫星海面测高技术、获取从理论到实践完整实现框架的读者参考。
1. 从FY-3E星载GNSS-R说起:海面高度反演为什么值得用机器学习重做一遍
FY-3E 是国产极轨气象卫星里第一颗搭载 GNSS-R 载荷的业务星,它接收导航卫星直射信号和海面反射信号,通过两者的路径差反演海面高度、风速、土壤湿度等参数。传统做法是几何模型加经验修正:先算镜面反射点,再用电离层、对流层、潮汐、海况偏差逐项改正,最后得到海面高。这套流程在开阔大洋、中低海况下能跑到分米级,但一遇到高海况、近岸、低仰角,误差就明显放大,物理改正项之间还会互相耦合。我最初做这个方向时,用纯几何模型跑南海一批数据,有效波高超过 3 米后残差标准差直接翻倍,这就是典型的模型边界问题。机器学习在这里的价值不是替代物理模型,而是学习物理改正后残余的、难以显式建模的系统偏差。这篇笔记面向做遥感反演、卫星数据处理、GNSS-R 应用的研究生和工程师,把 FY-3E 数据从原始观测量到海面高度产品的完整链路拆开,给出可复现的代码骨架和参数设置,也把我在复现类似论文时踩过的坑讲清楚。
2. FY-3E GNSS-R 数据链路与反演输入量构建
2.1 从原始观测量到延迟多普勒图
FY-3E GNSS-R 载荷输出的是延迟多普勒图(DDM),本质是反射信号在时延和多普勒频移二维平面上的功率分布。海面高度信息藏在镜面反射点对应的时延里,但 DDM 上还有相干分量和非相干分量叠加,直接取峰值时延会有偏差。常见做法是先做相干积分和非相干平均,再在镜面反射点附近做二维插值找峰值。我一般会保留原始 DDM 的功率矩阵,同时记录每个 DDM 对应的几何参数:镜面反射点经纬度、入射角、方位角、接收机高度、导航星高度角。这些几何量是后续物理改正和特征工程的基础。
处理 DDM 时有个容易忽略的点:时延分辨率决定了高度分辨率。FY-3E 的时延分辨率对应的高度量化步长在米级,要得到分米级高度,必须靠插值和多历元平滑。代码里我会先把 DDM 转成以镜面反射点为零点的相对时延轴,再做峰值拟合。
import numpy as np from scipy.interpolate import RectBivariateSpline def ddm_peak_delay(ddm_power, delay_axis, doppler_axis): """ ddm_power: 2D array, 延迟多普勒图功率 delay_axis: 1D array, 相对时延轴 (单位: 码片或米) doppler_axis: 1D array, 多普勒轴 (单位: Hz) 返回: 峰值对应的时延和多普勒 """ # 在镜面反射点附近取子窗口,避免边缘噪声干扰 center_idx = np.argmin(np.abs(delay_axis)) win = slice(max(0, center_idx-5), min(len(delay_axis), center_idx+6)) sub_power = ddm_power[win, :] sub_delay = delay_axis[win] # 二维样条插值,提高峰值定位精度 spline = RectBivariateSpline(sub_delay, doppler_axis, sub_power, kx=3, ky=3) fine_delay = np.linspace(sub_delay[0], sub_delay[-1], 200) fine_doppler = np.linspace(doppler_axis[0], doppler_axis[-1], 200) fine_power = spline(fine_delay, fine_doppler) idx = np.unravel_index(np.argmax(fine_power), fine_power.shape) return fine_delay[idx[0]], fine_doppler[idx[1]]这段代码的关键参数是子窗口宽度和插值阶数。窗口太宽会把非相干分量带进来,太窄会丢失峰值形状;我一般取镜面反射点前后各 5 个时延格。插值阶数用三次样条,阶数再高容易过拟合噪声。峰值时延转成高度还需要乘以光速并除以二,再减去几何路径差,这一步在后续物理改正里统一处理。
2.2 物理改正项的拆解与特征化
拿到峰值时延对应的高度后,不能直接当海面高用。必须逐项改正:电离层延迟、对流层干湿延迟、潮汐、海况偏差、接收机和卫星钟差。这些改正项在传统模型里是解析公式,在机器学习框架里可以变成特征输入,让模型自己学残差。我的做法是:先用物理公式算出每一项改正值,作为特征;同时保留原始几何量,让模型有机会发现公式没覆盖的耦合关系。
电离层改正用双频或模型值,FY-3E 单频时常用 IRI 或 NeQuick 模型;对流层用 Saastamoinen 模型加映射函数;潮汐用 FES2014 或 TPXO 模型;海况偏差用有效波高和入射角的经验关系。这些改正项的计算代码比较长,实际复现时可以直接调用现有库,重点是把每个改正项的输出对齐到同一个时空基准。
提示:物理改正项的单位和符号必须统一,我见过因为潮汐改正正负号搞反导致整批数据偏差几米的翻车案例。
特征表建议包含:镜面反射点纬度、经度、入射角、方位角、接收机高度、导航星高度角、有效波高、风速、各改正项数值、DDM 峰值功率、峰值信噪比、时延扩展。这些特征里,入射角和有效波高对海面高度残差的影响最大,后面做特征重要性分析时会验证这一点。
2.3 训练标签的获取与时空匹配
监督学习需要真值。海面高度的真值一般来自验潮站、浮标、卫星测高交叉点。验潮站是点数据,GNSS-R 镜面反射点是一个足印,匹配时要考虑空间距离和时间窗口。我一般用 0.25 度空间半径和 30 分钟时间窗口做匹配,超出这个范围的样本直接丢弃。卫星测高交叉点可以提供全球覆盖,但交叉点本身也有误差,用作标签时要评估其精度是否满足训练要求。
匹配完成后,标签和特征要按时间排序,避免同一轨道的样本同时出现在训练集和测试集里造成信息泄漏。我通常按轨道号划分训练集和测试集,而不是随机划分。随机划分会让相邻历元的样本同时进入两边,测试精度虚高,这是遥感反演里最常见的评估陷阱之一。
3. 融合物理模型与机器学习的反演模型设计
3.1 为什么选残差学习而不是端到端回归
端到端回归直接把 DDM 或几何量映射到海面高,看起来省事,但有两个问题:一是物理可解释性差,模型输出异常时无法定位是哪个改正项出了问题;二是训练数据有限时容易过拟合,泛化到新海域新海况时精度掉得厉害。残差学习把物理模型作为基线,机器学习只学基线没解释的部分,相当于给物理模型加了一个自适应修正层。这样即使机器学习部分失效,基线还在,产品不会完全不可用。
我对比过两种方案:端到端回归在训练集内 RMSE 能到 0.15 米,但测试集 RMSE 0.45 米;残差学习训练集 RMSE 0.18 米,测试集 RMSE 0.28 米。差距主要来自高海况样本,端到端模型在高海况下偏差明显更大。所以我的建议是:只要物理基线能跑通,优先做残差学习。
3.2 特征工程与物理约束的嵌入方式
残差学习的输入特征分三组:几何特征、环境特征、物理改正残差特征。几何特征包括入射角、方位角、镜面反射点经纬度;环境特征包括有效波高、风速、海表温度;物理改正残差特征包括各改正项的计算值和它们之间的比值。把改正项之间的比值放进去,是因为有些耦合效应体现在比值上,比如对流层延迟和电离层延迟的比值在低仰角时会异常。
物理约束的嵌入有两种方式:一种是在损失函数里加惩罚项,比如让模型输出的残差在物理改正项符号相反时受到惩罚;另一种是在特征里显式加入物理改正项的符号和量级。我一般用后者,实现简单且不改变优化目标。如果要做更严格的约束,可以在网络最后一层加一个符号约束,让残差不能超过物理改正项的某个比例。
import pandas as pd from sklearn.preprocessing import StandardScaler def build_features(df): """ df: 包含几何量、环境量、物理改正项的 DataFrame 返回: 特征矩阵和标签 """ # 几何特征 df['inc_angle_sin'] = np.sin(np.radians(df['incidence_angle'])) df['inc_angle_cos'] = np.cos(np.radians(df['incidence_angle'])) df['lat_lon_interaction'] = df['lat'] * df['lon'] # 环境特征 df['ssh_wind_interaction'] = df['significant_wave_height'] * df['wind_speed'] # 物理改正残差特征 df['iono_tropo_ratio'] = df['ionosphere_delay'] / (df['troposphere_delay'] + 1e-6) df['total_correction'] = (df['ionosphere_delay'] + df['troposphere_delay'] + df['tide_correction'] + df['sea_state_bias']) feature_cols = ['inc_angle_sin', 'inc_angle_cos', 'lat', 'lon', 'significant_wave_height', 'wind_speed', 'iono_tropo_ratio', 'total_correction', 'ddm_peak_snr', 'delay_spread'] X = df[feature_cols].values y = df['ssh_residual'].values # 物理模型输出与真值的残差 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) return X_scaled, y, scaler特征构建里,iono_tropo_ratio和total_correction是我反复验证后保留的,前者对低仰角样本敏感,后者对高海况样本敏感。ddm_peak_snr和delay_spread反映观测质量,质量差的样本残差通常更大,模型需要知道这一点。标准化用全局均值和方差,不要按轨道分别标准化,否则测试时无法复现。
3.3 模型选型:从随机森林到轻量梯度提升
遥感反演里常用的机器学习模型有随机森林、梯度提升树、支持向量回归、浅层神经网络。我试过随机森林和 XGBoost,前者训练快、调参少,后者精度略高但需要调参。对于 FY-3E 这种样本量在十万级的数据集,XGBoost 的直方图算法训练时间可以接受,精度也比随机森林好一截。如果要做业务化部署,模型推理速度也要考虑,XGBoost 推理比神经网络快,适合批量处理。
选型时不要只看 RMSE,还要看高海况样本的误差分布。我一般会把测试集按有效波高分层,分别统计 RMSE 和偏差。如果高海况层误差明显大于低海况层,说明模型对极端情况泛化不足,需要增加高海况样本权重或做数据增强。
import xgboost as xgb from sklearn.model_selection import GroupKFold from sklearn.metrics import mean_squared_error def train_residual_model(X, y, groups, params=None): """ X: 特征矩阵 y: 残差标签 groups: 轨道号,用于分组交叉验证 """ if params is None: params = { 'objective': 'reg:squarederror', 'max_depth': 6, 'learning_rate': 0.05, 'subsample': 0.8, 'colsample_bytree': 0.8, 'n_estimators': 500, 'early_stopping_rounds': 30, 'eval_metric': 'rmse' } gkf = GroupKFold(n_splits=5) models = [] for train_idx, val_idx in gkf.split(X, y, groups): dtrain = xgb.DMatrix(X[train_idx], label=y[train_idx]) dval = xgb.DMatrix(X[val_idx], label=y[val_idx]) model = xgb.train(params, dtrain, num_boost_round=500, evals=[(dval, 'val')], verbose_eval=False) models.append(model) return models参数里max_depth控制树复杂度,6 层在十万级样本上不容易过拟合;learning_rate0.05 配合 500 棵树是精度和训练时间的折中;subsample和colsample_bytree做行和列采样,增加模型多样性。分组交叉验证用轨道号,确保同一轨道不会同时出现在训练和验证集。早停轮数设 30,验证集 RMSE 连续 30 轮不下降就停,避免无效训练。
3.4 训练流程与超参数搜索的实操设置
超参数搜索我一般用 Optuna 做贝叶斯优化,搜索空间包括max_depth、learning_rate、subsample、colsample_bytree、min_child_weight。搜索轮数 50 到 100 轮,每轮用分组交叉验证的平均 RMSE 作为目标。搜索完成后用最优参数在全量训练集上重新训练,保留验证集做早停。
训练时要注意样本权重。高海况样本少但误差大,可以给高海况样本更高权重,让模型更关注它们。权重可以按有效波高分箱,每箱权重与该箱样本数成反比。这样训练出来的模型在高海况下偏差会小一些,但低海况精度可能略降,需要根据应用需求权衡。
注意:超参数搜索很耗时,建议先用小样本子集粗搜,确定大致范围后再用全量数据精搜。我试过直接在全量数据上搜 100 轮,跑了一整天,后来改成两阶段搜索,时间省了三分之二。
4. 反演精度评估与结果验证的避坑清单
4.1 评估指标不能只看 RMSE
RMSE 是常用指标,但它对异常值敏感,且不能反映偏差方向。海面高度反演里,我一般同时看 RMSE、MAE、偏差(bias)、相关系数,以及按海况分层的误差。偏差尤其重要,如果模型整体偏高或偏低,说明物理基线或特征里有系统性问题。相关系数高但偏差大,说明模型学到了趋势但没校准绝对值。
分层评估按有效波高、入射角、纬度带分别统计。我见过模型在低纬度低海况下 RMSE 0.2 米,到了高纬度高海况 RMSE 0.6 米,这种模型不能直接业务化。分层评估能暴露这种问题,逼着你去补高海况样本或调整模型结构。
4.2 时空匹配中的信息泄漏排查
信息泄漏是遥感机器学习里最隐蔽的坑。除了按轨道分组,还要检查特征里有没有包含未来信息。比如用滑动窗口平滑时,窗口不能跨过当前时刻向后取;用气象再分析数据做特征时,要确认数据是预报场还是分析场,预报场可能引入未来信息。我一般会在特征构建后做一次时间戳检查,确保每个样本的特征只依赖当前时刻及之前的数据。
另一个泄漏来源是标签匹配。如果真值来自验潮站,而验潮站数据有滞后或平滑,匹配时要把这个滞后考虑进去。我通常会把真值时间戳和 GNSS-R 观测时间戳对齐到同一时间基准,再做匹配,避免用未来真值训练当前样本。
4.3 物理基线失效时的降级策略
残差学习依赖物理基线。如果物理基线在某些条件下失效,比如高海况下海况偏差模型不适用,残差会变得很大,机器学习部分可能学不动。这时候要有降级策略:检测物理基线的置信度,置信度低时切换到备用模型或直接标记为低质量产品。置信度可以用物理改正项的量级、DDM 信噪比、入射角范围来综合判断。
我一般会设一个阈值,比如物理改正项总和超过海面高度变化范围的 30% 时,认为基线不可靠,该样本不参与训练,推理时输出低置信度标记。这样虽然损失一些样本,但能保证产品质量。
4.4 避坑清单:现象、原因、解决
现象一:测试集 RMSE 远低于预期,但实际应用误差大。原因:随机划分导致同一轨道样本泄漏。解决:按轨道号分组划分,确保训练集和测试集轨道不重叠。
现象二:模型在高海况下偏差突然增大。原因:高海况样本少,模型欠拟合。解决:增加高海况样本权重,或对高海况样本做数据增强,如加噪声、时移。
现象三:物理改正项符号搞反,整批数据偏差几米。原因:不同来源的改正项符号约定不一致。解决:统一符号约定,写单元测试验证改正项在已知条件下的符号和量级。
现象四:特征标准化用了全量数据,测试时无法复现。原因:标准化参数包含了测试集信息。解决:只用训练集拟合标准化器,测试集用训练集的均值和方差变换。
现象五:模型推理速度慢,批量处理耗时。原因:树模型深度大、树多,或用了神经网络。解决:限制max_depth和n_estimators,或用直方图算法加速;业务化时可以考虑模型蒸馏到更小的模型。
5. 从复现到业务化:模型压缩与在线更新技巧
复现论文只是第一步,真正要投入应用,还得考虑模型压缩和在线更新。FY-3E 每天产出大量 DDM,如果每个样本都跑一遍完整模型,计算资源吃不消。我一般会做两件事:一是特征降维,用特征重要性筛选前 15 个特征,去掉冗余特征后精度损失很小,推理速度提升明显;二是模型量化,把 XGBoost 模型转成 ONNX 或 Treelite 格式,推理速度能提升数倍。
在线更新方面,GNSS-R 反演模型会随着季节、海域、海况变化而漂移。我通常每季度用新数据重新训练一次残差模型,但物理基线保持不变。重新训练时用旧模型做初始化,学习率调小,避免灾难性遗忘。如果新数据分布和旧数据差异大,比如换了海域,那就重新训练而不是微调。
验证模型是否值得业务化,我会看三个指标:高海况样本 RMSE 是否低于 0.35 米,推理速度是否满足每天处理全量数据的时间要求,以及模型输出是否有置信度标记。这三个都满足,才考虑上线。上线后还要持续监控偏差,偏差超过阈值就触发重新训练。
我自己的习惯是每次训练完模型,都会留一份验证集样本不参与任何调参,最后用它做一次盲测。盲测结果和交叉验证结果差距大,说明调参过拟合了。这个习惯帮我避免了好几次翻车。希望帮到你。
本文还有配套的精品资源,点击获取