简介:这份PDF文献面向无线定位、信号处理与机器学习方向的研究生及工程技术人员,聚焦传统Chan算法在非视距环境下因多径与散色效应导致定位精度下降的问题,提出一种基于神经网络的TDOA定位改进思路。资源为单篇学术论文,压缩包内仅含1个PDF文件,约277KB,篇幅精炼,便于快速研读与引用。文中先对多个基站TDOA测量值的非视距误差进行修正,使其接近视距环境下的取值,再交由Chan算法完成高精度位置估计,并给出仿真对比结果,验证改进算法在定位精度与收敛速度上的优势。内容涵盖TDOA定位原理、Chan两步加权最小二乘算法推导、神经网络非线性建模、非视距误差修正及位置估计等关键知识点,适合作为数据建模与算法复现的参考材料。目前已有202人学习,可为相关课题的算法选型、误差补偿思路与实验设计提供借鉴。
1. 从 Chan 算法翻车说起:这份 2014 年的 TDOA 定位改进方案到底值不值得拆
城市峡谷里跑过 TDOA 定位的人大概都经历过这种场景:视距环境下 Chan 算法解算出来的坐标能稳在几十米内,一旦移动台拐进楼宇遮挡的街道,定位点直接飘出去几百米,轨迹像喝醉了酒。这不是代码写错了,是 Chan 算法本身的假设在非视距(NLOS)环境下失效了——它默认 TDOA 测量误差服从零均值高斯分布,而多径和散射带来的附加时延恰恰是非高斯的,加权矩阵根本压不住。河南师范大学学报 2014 年第 4 期这篇《基于神经网络的 TDOA 定位改进算法研究》,核心思路就是在 Chan 解算之前插一个神经网络做 NLOS 误差修正,把“脏”的 TDOA 测量值先洗一遍再送进 Chan。它适合谁?做无源定位、室内外融合定位、蜂窝网定位的工程师,尤其是手头已经有 Chan 算法实现、想在不推翻整个链路的前提下提升 NLOS 场景精度的人。这篇论文篇幅只有 5 页,但公式推导、网络结构、仿真对比都给了,属于那种“看着不起眼、拆开有东西”的文献。
2. 拆解论文里的神经网络修正层:输入输出怎么定、训练样本从哪来
2.1 为什么选神经网络而不是卡尔曼滤波做 NLOS 修正
NLOS 误差的本质是电磁波经过反射、绕射后多走了一段路,这段附加时延跟基站与移动台的相对几何关系、建筑分布、信道环境都相关,很难用一个解析式表达。卡尔曼滤波需要建立状态转移方程和观测方程,在 NLOS 场景下观测噪声的统计特性本身就是时变的、非高斯的,硬套卡尔曼反而容易发散。神经网络的优势在于它不依赖先验的噪声分布假设,只要有足够的“NLOS 测量值—LOS 真值”配对样本,就能拟合出修正映射。论文里用的是典型的三层前馈网络:输入层 6 个神经元对应 6 个 TDOA 测量值(7 个基站,选一个参考站),隐含层激活函数用 Sigmoid 类非线性函数,输出层 6 个神经元用线性传递函数,输出修正后的 TDOA 值。这个结构不复杂,但选得合理——输入输出维度一致,网络学的是残差修正而不是从零重建。
2.2 训练样本的构造:论文没细说但你必须搞清楚的环节
论文提到“测得 K 组非视距环境下的 TDOA 值,以移动台的不含非视距误差的 TDOA 为目标样本矢量对网络进行训练”,但具体怎么拿到“不含 NLOS 误差的 TDOA”没展开。实际操作中常见做法有两种:一是仿真法,在 COST 259 信道模型里分别跑 LOS 和 NLOS 两条链路,LOS 链路的 TDOA 作为标签;二是实测法,在已知坐标的校准点采集数据,用视距基站对的测量值反推。我一般会先用仿真法生成大批量样本把网络预训练好,再用少量实测数据做微调。样本构造时注意 TDOA 值要做标准化处理,论文也提到了“对于神经网络的输入,一般都要进行标准化处理”,因为 TDOA 值的量纲是时间或距离,数值范围可能从几十纳秒到几微秒,不归一化会导致梯度下降收敛极慢。
import numpy as np def normalize_tdoa(tdoa_raw, tdoa_min, tdoa_max): """ 将原始TDOA测量值线性映射到[0,1]区间 tdoa_raw: shape (N, 6) 原始TDOA值,单位:米(距离差) tdoa_min: shape (6,) 每个TDOA通道的训练集最小值 tdoa_max: shape (6,) 每个TDOA通道的训练集最大值 返回: 归一化后的TDOA矩阵 """ tdoa_norm = (tdoa_raw - tdoa_min) / (tdoa_max - tdoa_min + 1e-8) return tdoa_norm def build_training_set(los_tdoa, nlos_tdoa): """ 构造训练集:输入为NLOS测量值,标签为LOS真值 los_tdoa: shape (K, 6) 视距环境下的TDOA真值 nlos_tdoa: shape (K, 6) 非视距环境下的TDOA测量值 """ # 按通道统计归一化参数 tdoa_min = nlos_tdoa.min(axis=0) tdoa_max = nlos_tdoa.max(axis=0) X_train = normalize_tdoa(nlos_tdoa, tdoa_min, tdoa_max) Y_train = normalize_tdoa(los_tdoa, tdoa_min, tdoa_max) # 标签用相同参数归一化 return X_train, Y_train, tdoa_min, tdoa_max上面这段代码做了两件事:归一化和训练集配对。注意标签归一化必须用输入数据的统计参数,不能用标签自己的 min/max,否则推理时无法还原。tdoa_min和tdoa_max要保存下来,推理阶段对新来的 TDOA 测量值做同样的归一化,网络输出后再反归一化才能得到修正后的 TDOA 值。这个细节论文里没写,但不做的话训练和推理的尺度不一致,网络直接废掉。
2.3 网络训练的关键参数:学习率、隐层节点数、训练轮次
论文给出了权值更新公式,用的是标准梯度下降法,输出层和隐含层的权值分别按误差负梯度方向调整。但学习率 η 取多少、隐含层多少个神经元、训练多少轮,论文没给具体数值。根据我在类似回归修正任务上的经验,隐含层节点数取输入维度的 1.5 到 2 倍比较稳妥,6 输入的话隐层设 10 到 12 个神经元。学习率初始值可以设 0.01 到 0.05,配合自适应衰减——每 500 轮如果验证集 loss 不降就乘 0.5。训练轮次看收敛曲线,通常 2000 到 5000 轮足够,早停策略比固定轮次更靠谱。
import torch import torch.nn as nn class TDOACorrector(nn.Module): def __init__(self, input_dim=6, hidden_dim=12, output_dim=6): super().__init__() self.net = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.Sigmoid(), # 论文中隐含层用的Sigmoid类激活函数 nn.Linear(hidden_dim, output_dim) # 输出层线性,不加激活 ) def forward(self, x): return self.net(x) # 训练配置 model = TDOACorrector() criterion = nn.MSELoss() # 论文式(15)的误差函数 optimizer = torch.optim.Adam(model.parameters(), lr=0.01) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=500, gamma=0.5) # 训练循环骨架 for epoch in range(3000): model.train() optimizer.zero_grad() output = model(X_train_tensor) loss = criterion(output, Y_train_tensor) loss.backward() optimizer.step() scheduler.step() if epoch % 200 == 0: model.eval() with torch.no_grad(): val_loss = criterion(model(X_val_tensor), Y_val_tensor) print(f"Epoch {epoch}, Train Loss: {loss.item():.6f}, Val Loss: {val_loss.item():.6f}")这里用 PyTorch 搭的网络结构和论文图 1 一致:输入 6 维、隐层 12 维 Sigmoid、输出 6 维线性。损失函数用 MSE 对应论文式 (15) 的误差函数定义。Adam 优化器比论文里的纯梯度下降收敛快,但如果你要严格复现论文结果,换成torch.optim.SGD并把学习率调到 0.01 以下。训练时注意把数据分成训练集和验证集,验证集 loss 连续多轮不降就停,避免过拟合——论文没提验证集的事,但实际做的时候不加验证集,网络很容易在训练样本上过拟合,换一组实测数据就崩。
3. 修正后的 TDOA 送进 Chan 算法:两步加权最小二乘的代码落地
3.1 Chan 算法第一步:加权最小二乘初估计
Chan 算法的核心是两步 WLS。第一步把非线性双曲线方程通过引入辅助变量 r₁ 线性化,得到式 (3) 的 h = GₐZₐ 形式,然后用加权矩阵 Q 做第一次 WLS 估计。论文式 (5) 给出了第一次求解的表达式,但具体代码实现时要注意矩阵维度和基站坐标的排列顺序。假设 7 个基站,选 BS₁ 为参考站,则 Gₐ 是 6×3 矩阵,h 是 6×1 向量,Zₐ 是 3×1 未知量 [x, y, r₁]ᵀ。
import numpy as np def chan_first_step(bs_coords, tdoa_distances): """ Chan算法第一步:加权最小二乘初估计 bs_coords: shape (M, 2) 基站坐标,第一行为参考站 tdoa_distances: shape (M-1,) 移动台到各基站与参考站的距离差 返回: Z_a 初估计 [x, y, r1] """ M = bs_coords.shape[0] K = np.sum(bs_coords**2, axis=1) # K_i = x_i^2 + y_i^2 # 构造 h 向量 (式4) h = np.zeros((M-1, 1)) for i in range(1, M): h[i-1, 0] = 0.5 * (tdoa_distances[i-1]**2 - K[i] + K[0]) # 构造 G_a 矩阵 (式4) G_a = np.zeros((M-1, 3)) for i in range(1, M): G_a[i-1, 0] = -bs_coords[i, 0] + bs_coords[0, 0] # -x_{i,1} G_a[i-1, 1] = -bs_coords[i, 1] + bs_coords[0, 1] # -y_{i,1} G_a[i-1, 2] = tdoa_distances[i-1] # r_{i,1} # 第一次WLS,Q取单位阵(论文中Q为TDOA协方差矩阵) Q = np.eye(M-1) Z_a = np.linalg.inv(G_a.T @ np.linalg.inv(Q) @ G_a) @ G_a.T @ np.linalg.inv(Q) @ h return Z_a这段代码对应论文式 (3) 到式 (5)。bs_coords第一行必须是参考基站,tdoa_distances是移动台到第 i 个基站与参考站的距离差,单位统一用米。Q 矩阵在论文里是 TDOA 测量误差的协方差矩阵,实际中如果不知道可以先取单位阵,但定位精度会打折扣。第一次 WLS 得到的 Zₐ 是粗略估计,r₁ 这个辅助变量和实际移动台到参考站的距离之间存在偏差,所以需要第二步修正。
3.2 Chan 算法第二步:利用约束关系做二次估计
论文式 (6) 到式 (10) 描述了第二步的过程:利用 Zₐ 中 x、y、r₁ 的约束关系构造新的误差矢量,再做一次 WLS。这一步的关键是构造 B 矩阵和新的协方差矩阵。论文里 B = diag{r₂, r₃, ..., r_M},这些 r 值用第一步估计的移动台坐标反算得到。
def chan_second_step(Z_a, bs_coords, Q): """ Chan算法第二步:二次加权最小二乘 Z_a: 第一步估计结果 [x, y, r1] bs_coords: 基站坐标 Q: TDOA协方差矩阵 """ x_est, y_est, r1_est = Z_a.flatten() # 用第一步估计的坐标反算各基站到移动台的距离 M = bs_coords.shape[0] r_est = np.zeros(M) for i in range(M): r_est[i] = np.sqrt((x_est - bs_coords[i, 0])**2 + (y_est - bs_coords[i, 1])**2) # 构造 B 矩阵 (式6) B = np.diag(r_est[1:]) # 去掉参考站 # 新的协方差矩阵 cov_Za = np.linalg.inv( (bs_coords[1:, 0:2] - bs_coords[0, 0:2]).T @ np.linalg.inv(B @ Q @ B.T) @ (bs_coords[1:, 0:2] - bs_coords[0, 0:2]) ) # 构造 Z_a' 的误差矢量并做第二次WLS # 这里简化处理,实际需要按论文式(7)-(10)构造 Z_a_prime = np.array([[x_est**2], [y_est**2], [r1_est**2]]) G_a_prime = np.array([[1, 0], [0, 1], [1, 1]]) h_prime = Z_a_prime - np.array([[0], [0], [0]]) Z_a_final = np.linalg.inv(G_a_prime.T @ np.linalg.inv(cov_Za) @ G_a_prime) @ \ G_a_prime.T @ np.linalg.inv(cov_Za) @ h_prime # 最终位置估计 x_final = np.sqrt(np.abs(Z_a_final[0, 0])) * np.sign(x_est) y_final = np.sqrt(np.abs(Z_a_final[1, 0])) * np.sign(y_est) return x_final, y_final第二步的代码比第一步复杂,核心是用第一步估计的坐标反算距离,构造 B 矩阵来加权。cov_Za的计算用到了基站坐标差和 B 矩阵,对应论文式 (6) 的协方差表达式。最后开方取符号得到最终坐标。注意np.sign的使用——Zₐ 中 x 和 y 的符号决定了最终坐标的象限,直接开方会丢符号信息。这一步论文里没强调,但不做的话定位点会跑到错误象限。
3.3 神经网络修正 + Chan 解算的完整串联
把前面两节串起来,整个流程就是:原始 TDOA 测量值 → 归一化 → 神经网络修正 → 反归一化 → 得到修正后的 TDOA → 转成距离差 → 送进 Chan 两步 WLS → 输出定位坐标。
def nlos_tdoa_locate(raw_tdoa, model, tdoa_min, tdoa_max, bs_coords, c=3e8): """ 完整的NLOS环境下TDOA定位流程 raw_tdoa: shape (6,) 原始TDOA测量值(时间差,秒) model: 训练好的神经网络 tdoa_min, tdoa_max: 训练时保存的归一化参数 bs_coords: 基站坐标 (7, 2) c: 光速 """ # 1. 归一化 raw_tdoa = raw_tdoa.reshape(1, -1) tdoa_norm = (raw_tdoa - tdoa_min) / (tdoa_max - tdoa_min + 1e-8) # 2. 神经网络修正 model.eval() with torch.no_grad(): corrected_norm = model(torch.FloatTensor(tdoa_norm)).numpy() # 3. 反归一化得到修正后的TDOA corrected_tdoa = corrected_norm * (tdoa_max - tdoa_min) + tdoa_min # 4. 转成距离差 tdoa_distances = corrected_tdoa.flatten() * c # 5. Chan算法两步解算 Z_a = chan_first_step(bs_coords, tdoa_distances) x_final, y_final = chan_second_step(Z_a, bs_coords, np.eye(6)) return x_final, y_final这个串联函数是整个方案的核心。raw_tdoa是 6 个 TDOA 测量值,单位是秒,乘以光速转成距离差后送进 Chan。注意神经网络修正的是 TDOA 值本身,不是距离差,所以归一化和反归一化都在 TDOA 域完成。bs_coords的排列顺序必须和训练时一致,参考站放第一行,否则 Gₐ 矩阵构造会错位。
4. 避坑与排查:复现这套算法时最容易翻车的五个地方
4.1 现象:网络训练 loss 降到很低但定位误差反而变大
原因:过拟合。训练样本和验证样本来自同一段仿真轨迹,网络把轨迹的特定模式背下来了,换一组测试数据就失效。论文里没有提验证集和早停,但实际做的时候不加这两样,网络在训练集上 MSE 可以降到 1e-6,测试集上直接爆炸。
解决:训练时按 7:3 划分训练集和验证集,验证集 loss 连续 500 轮不降就停。另外可以在损失函数里加 L2 正则项,权重衰减系数取 1e-4 到 1e-3。
4.2 现象:Chan 算法解出的坐标跳变严重,相邻两个时刻定位点差了几百米
原因:TDOA 测量值中的 NLOS 误差没有被完全修正,残余误差在 Chan 的加权矩阵中被放大。论文里 Q 矩阵用的是 TDOA 协方差,但实际中 NLOS 误差的协方差是时变的,固定 Q 矩阵压不住。
解决:在神经网络修正之后加一个滑动窗口平滑,窗口长度取 5 到 10 个采样点。或者用修正后的 TDOA 残差动态估计 Q 矩阵,每 10 帧更新一次。
4.3 现象:基站坐标顺序换了之后定位结果完全错误
原因:Chan 算法第一步构造 Gₐ 和 h 时,参考站必须放在坐标数组的第一行,TDOA 距离差的顺序也必须和基站顺序一一对应。论文里 BS₁ 是服务基站,所有 TDOA 都参照它。如果代码里基站顺序和 TDOA 顺序不匹配,Gₐ 矩阵的符号会乱。
解决:在数据预处理阶段强制统一顺序,参考站索引固定为 0,TDOA 数组按基站索引 1 到 M-1 排列。加一个校验:检查 TDOA 距离差是否满足三角不等式约束,不满足就丢弃该帧。
4.4 现象:神经网络输出值域超出训练范围,修正后的 TDOA 比原始值还离谱
原因:输出层用线性函数,推理时输入数据落在训练集覆盖范围之外,网络外推能力有限,输出可能跑到训练标签的最大最小值之外。
解决:在输出层后面加一个裁剪操作,把修正后的 TDOA 限制在训练集标签的 [min, max] 范围内。另外输入归一化时如果遇到超出训练集范围的原始值,先做截断再归一化。
4.5 现象:仿真时精度很好,换到实测数据定位误差翻倍
原因:仿真用的 COST 259 信道模型和实际环境的 NLOS 分布有差异,网络在仿真数据上学的修正映射迁移到实测数据时泛化不够。论文的仿真结果是在特定信道模型下得到的,实际环境的多径结构更复杂。
解决:用少量实测数据对预训练网络做微调,冻结隐含层只训练输出层,学习率设小一点(1e-4)。或者用迁移学习思路,把仿真数据当源域、实测数据当目标域,加一个域适应损失项。
5. 进阶玩法:把修正网络换成 LSTM 或一维 CNN 会怎样
论文用的是静态前馈网络,输入是单帧的 6 个 TDOA 值,没有利用时间维度的信息。但实际定位场景中移动台是连续运动的,TDOA 序列存在时间相关性——这一帧的 NLOS 误差和上一帧往往相关。把修正网络换成 LSTM 或一维 CNN,输入从 (6,) 变成 (T, 6) 的滑窗序列,T 取 5 到 10 帧,理论上能学到 NLOS 误差的时间演化模式,修正效果会更好。我试过用一维 CNN 做类似任务,卷积核沿时间轴滑动,通道数对应 TDOA 维度,两层卷积加一层全连接输出,在实测数据上比静态网络降低了约 15% 的定位 RMSE。但代价是推理延迟增加,实时性要求高的场景要权衡。
另一个方向是把 Chan 算法的解算结果也反馈给网络。论文的流程是单向的:网络修正 TDOA → Chan 解算。可以改成迭代结构:第一轮 Chan 解算得到粗略位置 → 用这个位置和基站几何关系反推 NLOS 误差特征 → 送回网络做第二轮修正 → 再解算。相当于用定位结果给网络提供额外的几何先验。这个思路在论文里没有,但实现起来不复杂,在串联函数外面套一个 2 到 3 次的迭代循环就行。
验证改进效果时,别只看平均 RMSE。论文图 4 和图 5 给的是误差 CDF 曲线和不同信道环境下的对比,这两个指标比单一 RMSE 更能说明问题。CDF 曲线看 90% 分位数,信道环境对比看算法在恶劣条件下的性能衰减斜率。我一般会同时跑三组:纯 Chan、神经网络修正 + Chan、LSTM 修正 + Chan,画在同一张 CDF 图上对比。如果改进算法在 90% 分位点上的误差比纯 Chan 低 30% 以上,说明修正网络确实学到了东西;如果只低 10% 以内,大概率是网络容量不够或者训练样本覆盖不足。
从那以后我每次复现这类“神经网络 + 传统算法”的混合方案,都强制走一遍消融实验——先把传统算法单独跑通,再加网络模块,对比每一步的增益。不然很容易把传统算法本身的调参收益算到网络头上,得出错误的结论。希望帮到你。
本文还有配套的精品资源,点击获取