MCM520 ← 资料站首页 太阳影子定位的非线性最小二乘反演(2015A 范文二) 打开交互阅读器 →

太阳影子定位的非线性最小二乘反演(2015A 范文二)

一、反演问题的数学形式化

范文一给出了正演模型 (clock,φ,λ,δ,H)↦(x,y)( \text{clock},\varphi,\lambda,\delta,H )\mapsto(x,y)。反演即其逆:已知一组观测 (clocki,xi,yi)(\text{clock}_i,x_i,y_i) 与杆高 HH,求参数 θ=(φ,λ,δ)\boldsymbol{\theta}=(\varphi,\lambda,\delta) 使模型尽可能吻合观测。这是一个典型的非线性最小二乘问题。

记第 ii 个时刻模型预测为 (xim(θ),yim(θ))(x_i^{\rm m}(\boldsymbol{\theta}),y_i^{\rm m}(\boldsymbol{\theta})),定义残差向量与平方和目标函数
ri=(xim−xi, yim−yi),S(θ)=∑i=1N∥ri∥2\mathbf{r}_i=(x_i^{\rm m}-x_i,\ y_i^{\rm m}-y_i),\qquad S(\boldsymbol{\theta})=\sum_{i=1}^{N}\|\mathbf{r}_i\|^2
最小化 S(θ)S(\boldsymbol{\theta}) 即得到地理参数估计。由于 xm,ymx^{\rm m},y^{\rm m} 对 (φ,λ,δ)(\varphi,\lambda,\delta) 高度非线性,SS 存在多个局部极小,需采用带多重启动的迭代法。

二、Levenberg–Marquardt 算法

本文自写纯 Python 的 LM 算法(无 numpy/scipy 依赖),其核心迭代式为
(J⊤J+λI) Δθ=−J⊤r(\mathbf{J}^\top\mathbf{J}+\lambda\mathbf{I})\,\Delta\boldsymbol{\theta}=-\mathbf{J}^\top\mathbf{r}
其中 J\mathbf{J} 为残差对参数的雅可比矩阵(本文用中心差分数值估计),λ\lambda 为阻尼因子:当步长使 SS 下降时减小 λ\lambda 趋向高斯–牛顿法以加速;否则增大 λ\lambda 趋向梯度下降以保证稳定。

多重启动避免局部极小:以网格初值 (φ0,λ0,δ0)∈{20,30,39,45,55}∘×{100,108,116,120,125}∘×{15,20,22,23,23.45,23.6}∘(\varphi_0,\lambda_0,\delta_0)\in\{20,30,39,45,55\}^\circ\times\{100,108,116,120,125\}^\circ\times\{15,20,22,23,23.45,23.6\}^\circ 共 150 组分别迭代,取 SS 最小者为最终解。图 2 显示绝大多数初值收敛到同一个极小(SSE≈0.0026),少数初值落入次极小(SSE 明显更大),说明多重启动的必要性。

图2

图1

图 1 给出从一组代表性初值出发的 SSE 收敛曲线:约 20 步内从 S≈102S\approx 10^2 量级骤降到 S<10−2S<10^{-2},收敛稳健迅速,体现 LM 对非线性最小二乘的高效性。

二之补、雅可比结构与收敛性分析

LM 的效率取决于雅可比 J\mathbf{J} 的质量。对本文模型,残差对三参数的偏导有明确的物理含义:∂r/∂φ\partial\mathbf{r}/\partial\varphi 反映纬度变化如何改变全天影子的整体曲率;∂r/∂λ\partial\mathbf{r}/\partial\lambda 反映经度(即时角相位)变化如何平移影子轨迹;∂r/∂δ\partial\mathbf{r}/\partial\delta 反映赤纬变化如何调制影长振幅。这三者方向近似正交,使 J⊤J\mathbf{J}^\top\mathbf{J} 条件数良好、梯度指向明确,故 LM 能快速下降。

数值上采用中心差分估计 J\mathbf{J}(步长 10−410^{-4}),兼顾精度与稳定性;阻尼因子 λ\lambda 以 2 倍率增减,每步若 SSE 下降则接受并更新、否则加大阻尼回到梯度下降方向。该策略在接近解时自动退化为高斯–牛顿法(二阶收敛),在远离解时退化为最速下降(保证全局下降),兼顾速度与稳健。对本文 13 个观测点、3 个参数的规模,单次反演耗时毫秒级,多重启动 36 组亦在百毫秒内完成,完全满足赛题实时性需求。

三、反演结果

对本文 13 个带噪观测点(噪声 σ=0.02 m\sigma=0.02\ \text{m})反演得到
φ^=39.714∘,λ^=116.346∘,δ^=23.271∘,Smin⁡=0.00257\hat\varphi=39.714^\circ,\quad \hat\lambda=116.346^\circ,\quad \hat\delta=23.271^\circ,\quad S_{\min}=0.00257
与真值 (φ,λ,δ)=(39.9∘,116.4∘,23.45∘)(\varphi,\lambda,\delta)=(39.9^\circ,116.4^\circ,23.45^\circ) 高度吻合,三项参数误差分别为 ∣Δφ∣=0.186∘|\Delta\varphi|=0.186^\circ、∣Δλ∣=0.054∘|\Delta\lambda|=0.054^\circ、∣Δδ∣=0.179∘|\Delta\delta|=0.179^\circ。图 6 直观对比真值与估计,二者柱高几乎不可分。

图6

拟合优度方面,图 3 将观测影子坐标(红)与反演模型预测(蓝)叠绘,二者点群几乎重合;图 4 进一步比较影长 rr 的逐时刻观测值与拟合值,最大偏差不足 0.05 m0.05\ \text{m},说明反演已逼近观测噪声水平——残差主要来自加性测量噪声而非模型偏差。

图3

图4

图 8 把 36 组初值的收敛落点画在 (φ,λ)(\varphi,\lambda) 平面上:红线(落入全局极小)紧密聚拢在 (39.7∘,116.3∘)(39.7^\circ,116.3^\circ) 附近,灰点(局部极小)则散布他处,从几何上解释了为何需要多重启动筛选。

图8

三之补、残差分析与模型充分性

反演得到 Smin⁡=0.00257S_{\min}=0.00257 对应均方根残差 S/N≈0.014 m\sqrt{S/N}\approx0.014\ \text{m},与施加的观测噪声 σ=0.02 m\sigma=0.02\ \text{m} 同量级,说明残差主要来自测量噪声而非模型结构性偏差——模型已被数据"充分解释",不存在显著的未建模效应。若残差明显大于噪声水平,则提示正演模型假设(如忽略大气折射、时差方程、杆底未严格水平)可能不成立,需扩充模型。

进一步,残差的均方根与噪声水平之比接近 1/21/\sqrt{2} 量级,符合"最小二乘在恰当模型下残差方差≈噪声方差"的统计预期,从侧面佐证了本文几何模型与噪声设定的自洽性。这一诊断在真实赛题中同样适用:用残差是否逼近测量精度,可快速判断模型是否"过拟合"或"欠拟合"。

四之补、观测窗口对可辨识性的影响

反演精度强烈依赖观测时段覆盖度。理论上,若只取正午单点,则只能确定该时刻的影长与方向,纬度、经度、日期三者高度耦合、不可分离;随观测窗口向早晚扩展,时角扫过更大范围,经度(相位)与赤纬(振幅)的效应逐渐解耦,参数可辨识性显著改善。本文取 9:00–15:00 共 6 小时、13 个时刻,已足以把三项参数均约束到亚度精度。

若实际仅能获得更短的片段(如受云层遮挡),可考虑:① 优先保留跨越正午且尽可能长的前后对称时段,以最大化时角跨度;② 若有多日数据,跨日联合反演可利用赤纬的缓慢漂移进一步分离纬度与日期;③ 对无法分离的参数(如钟差与经度),引入额外先验或参照时刻。这些策略的核心是让待估参数在观测中呈现可区分的"指纹",与范文一的灵敏度分析一脉相承。

四、由赤纬反推日期

得到 δ^=23.271∘\hat\delta=23.271^\circ 后,利用范文一的关系 δ(n)=23.45∘sin⁡(360∘(284+n)/365)\delta(n)=23.45^\circ\sin(360^\circ(284+n)/365) 反推日序。由于该函数在全年非单调,直接反三角函数会丢失分支;本文采用全局搜索:在 n∈[1,365]n\in[1,365] 上取使 ∣δ(n)−δ^∣|\delta(n)-\hat\delta| 最小的日序,得 n^=165\hat n=165。图 5 显示估计赤纬横线与 δ(n)\delta(n) 曲线在 n≈165n\approx165 处相交,对应夏至附近的夏半年分支,与真实 n=172n=172 仅差 7 天。

图5

7 天偏差源于观测噪声使 δ^\hat\delta 略低于真值 23.45∘23.45^\circ;在 σ=0.02 m\sigma=0.02\ \text{m} 量级下属于合理范围。若要求更精确的日期,可增加测量点或采用多日数据联合反演。

需特别说明的是,全局搜索返回的 n^=165\hat n=165 是距离估计赤纬最近的夏半年解;由于 δ(n)\delta(n) 关于夏至对称,冬半年存在一个完全对称、赤纬相同的另一支(约 n≈180n\approx180 之外的对跖冬支),二者在数学上等价。本文选取夏半年支的依据是:① 估计赤纬 23.271∘23.271^\circ 接近年度最大值,物理上只可能出现在夏半年附近;② 若引入"拍摄于夏半年"这一弱先验(如植被、光照背景),分支即可唯一确定。在缺乏任何季节信息时,单日影子确实无法区分两个分支,这是问题的固有多解性,而非算法缺陷——范文三将从不确定性角度进一步剖析。

五、由正午时刻反演经度

除联合最小二乘外,经度还可由正午最短影时刻独立估计,原理如下:太阳位于正南(影子最短、指向正北)时本地太阳时恰为 12:00,而相机采用 UTC+8(参考经度 120∘120^\circE),故本地太阳时 LST=clock−8+λ/15\mathrm{LST}=\text{clock}-8+\lambda/15,令其等于 12 得
λ=15∘(20−tnoon)\lambda=15^\circ\bigl(20-t_{\rm noon}\bigr)
其中 tnoont_{\rm noon} 为影子最短对应的相机时钟(经抛物插值细化)。图 7 给出拟合影长随时钟的曲线,其最低点即为 tnoont_{\rm noon},代入可得经度。该方法物理直观、计算量小;对本文数据细化得 tnoon≈12.17 ht_{\rm noon}\approx12.17\ \text{h},对应 λ≈117.4∘\lambda\approx117.4^\circ,与联合最小二乘的 116.3∘116.3^\circ 及真值 116.4∘116.4^\circ 量级一致(残余偏差来自 0.5 h 离散采样与噪声),两种方法相互印证。

图7

正午时刻法的局限在于它仅利用"最短影"这一个特征,对采样密度较为敏感:若观测时刻未能充分贴近正午、或离散间隔过大,抛物插值的细化精度会下降,从而导致经度估计偏差。因此本文将其定位为"独立校验"而非主反演,主反演仍由联合 LM 承担——二者结果量级一致,恰好构成互相印证的双保险。这也提示一个通用原则:当存在两种以上可独立求解同一参数的方法时,应优先让它们相互验证,而非盲目信任其中某一种。

六、算法小结与适用性

本文形成"正演模型 + LM 最小二乘 + 多重启动 + 日期/经度映射"的完整反演链路:

  1. 以影子坐标残差平方和为目标,LM 高效收敛;
  2. 多重启动规避局部极小,保证解的全球性;
  3. 反演三项参数误差均小于 0.2∘0.2^\circ,拟合残差逼近噪声水平;
  4. 赤纬经全局搜索映射为日序,正午时刻给出经度的独立校验。

该框架为纯解析—数值混合方法,无需有限元或黑箱优化器,可直接嵌入赛题求解流程。与通用黑箱优化器(如遗传算法、粒子群)相比,LM 充分利用了残差的梯度结构,收敛速度快一至两个数量级,且解满足一阶最优性条件、便于后续不确定性传播;代价是需关注初值与局部极小,本文以低成本的多重启动予以化解。

综合而言,2015A 的反演属于"中等规模光滑非线性最小二乘",LM 是该结构下的最优选择:既不像单纯梯度法那样依赖精细调参,也不像全局随机搜索那样浪费计算。在赛题限时环境下,这种"精确梯度 + 多重启动保底"的组合兼具可靠性与效率,是值得借鉴的解题范式。需要提醒的是,反演质量的天花板由正演模型决定——若范文一的几何公式存在近似(如忽略大气折射、时差方程),反演再精细也无法弥补模型偏差,因此"先建对模型、再反演"的顺序不可颠倒。下一篇(范文三)将评估测量噪声下的稳健性,并讨论赤纬半年度歧义、多地点可分辨性等工程问题。

附录:LM 反演复现(可运行)

import sys, os, math, statistics as ST
sys.path.insert(0, os.path.join("..", "..", "..", "tools"))
import gen_data as GD

D = GD.gen_2015a()
obs, H = D["obs"], D["H"]
(phi_f, lam_f, delta_f), sse = GD.invert_location(obs, H)
print("反演: φ=%.3f° λ=%.3f° δ=%.3f°  SSE=%.5f"
      % (phi_f, lam_f, delta_f, sse))
print("真值: φ=%.2f° λ=%.2f° δ=%.3f°"
      % (D["phi_true"], D["lam_true"], D["delta_true"]))
print("绝对误差 |Δφ|=%.3f° |Δλ|=%.3f° |Δδ|=%.3f°"
      % (abs(phi_f-D["phi_true"]), abs(lam_f-D["lam_true"]), abs(delta_f-D["delta_true"])))
# 赤纬 -> 日序(全局搜索避开半年度歧义)
n_est = min(range(1, 366), key=lambda nd: abs(GD.declination(nd)-delta_f))
print("由 δ=%.3f° 反推日序 n=%d(真值 %d)" % (delta_f, n_est, D["n_true"]))
# 正午时刻 -> 经度
noon = D["noon_clock"]
lam_noon = 15.0 * (20.0 - noon)
print("正午时钟=%.2f h -> 经度 λ=%.2f°(独立校验,真值 %.1f°)" % (noon, lam_noon, D["lam_true"]))