☰
船舶航迹聚类:Hausdorff距离函数与DBSCAN参数避坑
2026/10/29 22:47:25 网站建设 项目流程

简介:围绕船舶航迹聚类与异常行为识别,这套Matlab工程复现了《基于轨迹聚类的船舶异常行为识别研究》的核心流程,适合学习DBSCAN、豪斯多夫距离计算及航迹预测的学生与研究人员作为可运行案例。压缩包共20个文件,约4.32MB,以14个m脚本为主体,另含mat数据、结果压缩包、示意图和说明文档,覆盖航迹提取、均值滤波、DBSCAN聚类、改进Hausdorff距离计算、聚类中心提取、阈值分类与寻优、航迹偏离预测等模块。已有767人学习,代码完整、可直接运行,读者既能按模块理解距离度量与聚类参数的影响,也能基于现有流程替换航迹数据或调整模块,扩展为自己的船舶航迹分析模型,并借助绘图与误差统计脚本复现聚类效果和预测准确率。

1. 航迹聚类的真正关键:聚类算法只有三十行,距离函数却决定了全部结论

很多人第一次做船舶航迹聚类,会默认把 AIS 报文里的经纬度当成普通二维点,直接套一个 DBSCAN。实际上跑完之后常常发现,同一段航道上的上下行船被混成一团,速度、航向完全不同的两条轨迹在坐标上几乎重合,也被判成邻居。DBSCAN 本身没有错,它只是一台按半径数邻居的状态机,真正决定谁和谁算「近」的,是喂给它的那条距离函数。Matlab 做航迹聚类、给航迹预测做前置清洗这条链路里,最容易被跳过、也最容易踩坑的一环,就是把「两条轨迹之间的距离」定义清楚。Hausdorff 距离是这套定义里最常被拿来的起点,它简单、对称、不要求两条轨迹采样点对齐,但它对单个异常点的敏感程度,足以让一条跳变记录毁掉整个簇结构。

2. Hausdorff 距离的本质:一次 max-min 的极端值博弈

2.1 从欧氏距离到集合距离:Hausdorff 到底在度量什么

点与点之间的距离很好理解,两点之间直线最短。但一条航迹不是一个点,它是一串带时间戳的坐标,长度从几十到几千不等,采样间隔可能两秒也可能三分钟。两条航迹之间的「距离」,本质上是在问一个集合论问题:集合 A 整体偏离集合 B 有多远。

Hausdorff 给出的答案是分两步走的。先固定 A 里的某一个点 a,在 B 里找离它最近的那个点,量出这个最近距离;然后对所有 a 取最大值。写成公式就是:

h(A, B) = max over a in A of ( min over b in B of ||a - b|| )

这个式子的语义非常具体:A 上偏离 B 最远的那一处,偏离了多少。换成航迹的语言,就是两条船走过的路线,在最不吻合的那个位置上差了多远。如果 A 是实际轨迹,B 是一条标准航道中心线,那 h(A,B) 就是这条船在这段航程里偏离中心线的最大幅度。

Matlab 里最朴素的实现只有四行,核心是pdist2生成的点对距离矩阵:

function d = hausdorffClassic(A, B) % A: n x 2,轨迹点坐标,单位必须是米(不是经纬度度) % B: m x 2 D = pdist2(A, B); % n x m 的点对欧氏距离矩阵 dAB = max(min(D, [], 2)); % A 中每点到 B 的最近距离,再取最大 dBA = max(min(D, [], 1)); % B 中每点到 A 的最近距离,再取最大 d = max(dAB, dBA); % 双向取大,得到对称距离 end

min(D, [], 2)沿着列方向压缩,得到 n×1 的「每个 A 点到 B 的最近距离」;外层max把它压成一个标量,这就是单向 Hausdorff。参数上唯一需要小心的是坐标单位,pdist2只认数值,它不知道你给的是度还是米,这一点后面会单独展开。

2.2 有向 Hausdorff 与对称化:为什么必须取两次最大值

单向的 h(A,B) 有个容易被忽略的性质:它不对称。h(A,B) 和 h(B,A) 可以差出一个数量级,而且这种偏差不是随机噪声,是结构性的。

设想一条长航迹 A(500 个点,覆盖 80 公里)和一条短航迹 B(40 个点,只覆盖其中 5 公里的一段)。计算 h(B,A) 时,B 的每个点都能在 A 上找到很近的对应点,结果可能只有十几米。但计算 h(A,B) 时,A 上那 460 个落在 B 覆盖范围之外的点,每一个到 B 的最近距离都是几十公里,取最大值后 h(A,B) 直接爆到几十公里。

这个不对称性带来两个后果。第一,DBSCAN 的邻域查询D(i,:) <= eps需要距离矩阵是良定义的,如果 D(i,j) ≠ D(j,i),那么「i 是 j 的邻居」和「j 是 i 的邻居」会得出不同答案,簇的扩张过程就依赖于遍历顺序,跑两次结果不一样。第二,单向 Hausdorff 天然偏向点数多的一方,点数越多,取 max 的机会越多,距离越大,这是个纯统计效应,跟航迹形状没关系。

所以对称化不是可选项,是必选项:

H(A, B) = max( h(A, B), h(B, A) )

取两者的大值而不是平均值,是为了保持「最坏情况」的语义。如果取平均,一条被另一条完全包含的短航迹,会得到一个很小的距离,这在实际业务里可能是对的(短航迹确实贴着长航迹走),但在聚类里会让「包含关系」和「重合关系」混为一谈。

这里有个设计上的细节值得说清楚:经典的、双向取 max 的 Hausdorff 距离,在紧集空间上是一个真正的度量,满足非负性、同一性、对称性和三角不等式。三角不等式这条性质很重要,它意味着你可以用 BK-tree、Ball-tree 之类的索引结构做剪枝加速。但后面要讲的改进版本会破坏这条性质,这是一个必须提前知道的代价。

2.3 一个离群点如何摧毁整条距离:数值演示

Hausdorff 最致命的问题藏在那个max里。最大值运算没有任何鲁棒性可言,一个极端值就能主导整个结果。

构造一个最小可复现的例子。两条本该几乎重合的航迹,各 200 个点,正常位置的点对之间距离都在 10 米以内,但 A 上第 73 个点因为多径反射跳到了 3 公里外:

rng(7); t = linspace(0, 1, 200)'; B = [t * 1000, sin(t * 2 * pi) * 50]; % 基准航迹,1 km 长 A = B + randn(200, 2) * 3; % 正常抖动 3 m A(73, :) = A(73, :) + [3000, 0]; % 单点跳变 3 km d = hausdorffClassic(A, B); fprintf('Hausdorff = %.1f m\n', d); % 输出约 3000 m

跑出来的距离是 3000 米量级,而其余 199 个点的偏差都在 10 米以内。如果 eps 设成 500 米,这两条实际同航线的轨迹会被判成互不相邻,各自成簇;如果 eps 放宽到 3500 米,噪声轨迹又会把不相干的簇桥接起来。一个点,就让参数选择失去了可用的窗口。

这不是数据质量问题,是度量本身的函数形式问题。max-min 结构决定了它对「最坏的那个点」全额计费,而 AIS 数据里单点跳变、漂移、时间戳错乱都是常态。改进的方向也就很明确了:要么给最坏情况设一个上限,要么把「最坏」换成更稳健的统计量。

3. 改进的 Hausdorff:把「最坏情况」控制成「可控的最坏情况」

3.1 截断型与分位型:给极端值设一个上限

最直接的改法是截断(truncated / censored Hausdorff)。思路是:单个点的偏差超过 K 之后,就不再计入,统一按 K 处理。

h_K(A, B) = max over a in A of ( min( min over b in B of ||a - b||, K ) ) H_K(A, B) = max( h_K(A, B), h_K(B, A) )

代码改动只有两处:

function d = hausdorffTrunc(A, B, K) % K: 截断阈值,单位与坐标一致(米) % 语义:任何单点偏差超过 K 的,都只按 K 计 D = pdist2(A, B); dAB = max(min(min(D, [], 2), K)); % 先截断再取 max dBA = max(min(min(D, [], 1), K)); d = max(dAB, dBA); end

内层min(D, [], 2)得到每个点的最近距离,min(..., K)逐元素截断,外层max再取最大。这样单点跳变的贡献被压到 K,199 个正常点的结构信息得以保留。

K 的取值不是随便拍脑袋的。如果水域里 GPS 定位精度的 99 分位是 30 米,航线宽度是 500 米,那 K 取 200 到 300 米比较合理:既能把跳变点的影响压住,又不至于把「偏出航道」这种真实差异也一起抹平。K 取太小,所有轨迹都变得相似,簇数塌缩成一;K 取太大,退化成经典 Hausdorff,改进失效。

另一种改法是不截断,改取分位数。把「取最大」换成「取第 95 百分位」:

dAB = quantile(min(D, [], 2), 0.95); % 忽略最极端的 5% 点

分位版本对离群点更宽容,但它有个隐患:当两条航迹的形状差异恰好集中在尾部 5% 的点上时(比如一条船在末端拐进支流),这部分真实结构会被完全丢弃。截断版本至少还给一个 K 的保底值,分位版本是直接不要了。实际用下来,截断更可控,分位更适合做粗筛。

注意:截断后的 Hausdorff 不再满足三角不等式。举例来说,A 和 C 都很接近 B,但 A 和 C 之间可能隔得很远,截断把这段远距离压成了 K,等式两边就对不上了。这意味着你不能拿它去做树形索引的剪枝,只能老老实实算距离矩阵。

3.2 特征加权:把航向、速度、长度塞进距离里

纯几何的 Hausdorff 只看空间位置,这在船舶场景里是不够的。同一条航道,上行船和下行船的轨迹在坐标上高度重合,但航向差 180 度,业务上绝不该聚成一类。同样,锚泊船的轨迹是一个小抖动圈,和正常航行轨迹的形状差异在 Hausdorff 下可能很小,但速度分布完全不同。

改进的常见做法是把距离做成加权组合,几何项加语义项:

D(A, B) = w1 * H_K(A, B) / s_H + w2 * |Δcog| / 180 + w3 * |Δsog| / s_sog + w4 * d_endpoint(A, B) / s_E

每一项都做了归一化,量纲统一到 [0,1] 附近,权重才有可比性。Δcog是平均航向差,Δsog是平均对地速度差,d_endpoint是起点和终点的匹配距离(起点对起点、终点对终点,取和)。这一项很关键:Hausdorff 对「位置偏移」很敏感,但对「方向反转」几乎不敏感,而端点匹配能捕捉到方向信息。

function d = trajDist(A, B, opt) % A, B: n x 4,列 = [x, y, sog(m/s), cog(deg)] % opt: 结构体,含 K, w, s_H, s_sog, s_E dGeo = hausdorffTrunc(A(:,1:2), B(:,1:2), opt.K) / opt.s_H; dcog = abs(circDist(A(:,4), B(:,4))); % 航向差取环形距离 dsog = abs(mean(A(:,3)) - mean(B(:,3))); dEnd = (norm(A(1,1:2) - B(1,1:2)) + norm(A(end,1:2) - B(end,1:2))) / opt.s_E; d = opt.w(1) * dGeo + opt.w(2) * mean(dcog)/180 ... + opt.w(3) * dsog/opt.s_sog + opt.w(4) * dEnd; end function d = circDist(a, b) % 航向角的环形距离,处理 359 度和 1 度只差 2 度的情况 d = abs(mod(a - b + 180, 360) - 180); end

航向差必须用环形距离,直接相减会把 359 度和 1 度算成 358 度的差异,这是航迹处理里最常见的低级错误之一。归一化常数s_H、s_sog、s_E建议直接用数据的 95 分位数,而不是靠经验值,这样不同水域、不同船型的数据集之间可以复用同一套权重。

权重w的选取确实有点玄学的成分,但有个可操作的原则:先让w1 = 1,其余全为 0,跑一遍看簇的数量和形状;然后逐项加入,每次只调一个权重,观察簇的变化是否符合业务直觉。如果加了一项之后簇结构完全崩塌,说明这一项的归一化尺度不对,或者这项特征在这个数据集上根本没有区分度。

3.3 复杂度优化:把 O(N²n²) 拉回可算范围

距离矩阵的计算量是最容易低估的部分。N 条轨迹两两配对,每次 Hausdorff 要做 n×m 次点对距离,总复杂度是 O(N² n m)。代入一个中等规模的数据集:5000 条轨迹,每条平均 300 个点,需要 5000²/2 × 300² ≈ 1.1×10¹² 次距离计算。Matlab 里就算全向量化,也是几个小时起步,内存上pdist2一次要开 300×300 的矩阵,还好,但循环 1250 万次的开销是实打实的。

三个层次的优化,按投入产出比排序。

第一层是抽稀。Douglas-Peucker 把每条轨迹压到 20 到 40 个点,保留形状的关键拐点,点数降一个数量级,总计算量直接降两个数量级。航迹聚类的目标本来就是「走的是不是同一条路」,不是「逐点吻合到什么程度」,抽稀带来的精度损失在聚类场景里完全可以接受。抽稀容差建议取 GPS 噪声水平的 2 到 3 倍。

第二层是粗筛。先用一个便宜的、必然小于真实距离的下界做过滤,超过 eps 的直接不进入精细计算。最便宜的下界是起点距离和终点距离的较小值,或者轨迹包围盒中心距离减去两个包围盒半径:

% 粗筛:包围盒中心距离减半径,是 Hausdorff 的合法下界 cA = (min(A) + max(A)) / 2; rA = norm(max(A) - min(A)) / 2; cB = (min(B) + max(B)) / 2; rB = norm(max(B) - min(B)) / 2; lb = max(0, norm(cA - cB) - rA - rB); if lb > eps, d = Inf; return; end % 不可能成为邻居,直接跳过

第三层是并行。Matlab 的parfor在距离矩阵上有个坑:不能对矩阵做D(i,j) = ...这样的非切片赋值,会触发分类错误。正确写法是先按行算到 cell,最后拼接:

rows = cell(N, 1); parfor i = 1:N r = inf(1, N); for j = 1:N if j ~= i r(j) = trajDist(traj{i}, traj{j}, opt); end end rows{i} = r; end D = vertcat(rows{:}); D = min(D, D'); % 数值对称化,消除浮点误差带来的微小不对称

最后的min(D, D')看着多余,实际上是必须的:浮点误差会让 D(i,j) 和 D(j,i) 差最后几位,而 DBSCAN 的<= eps判断是精确比较,一条恰好卡在 eps 边界上的轨迹可能单向可达、反向不可达,导致簇的结果不稳定。

4. DBSCAN 在轨迹距离矩阵上的运行机制

4.1 eps 与 MinPts 在轨迹空间里的几何含义

点空间里 eps 是一个圆的半径,轨迹空间里它变成了一个语义完全不同的东西:两条轨迹在最不吻合处的偏差上限。eps = 300 米,意思就是「这两条船走的路线,任何一处偏离都不超过 300 米」,它们才算邻居。

这个语义转换带来一个直接的后果:eps 的合理量级由航道宽度和 GPS 精度共同决定,不是靠调参试出来的。内河航道窄,eps 可能只有 50 米;开阔海域航道宽,eps 到 1 公里也不奇怪。用一套参数跑所有水域,结果一定不可用。

MinPts 的含义倒没变,仍然是「一个核心点的邻域里至少有多少个样本」。但在轨迹场景下,这个参数的物理意义比点云更清晰:它约等于「一条主航线上至少有多少条船走过,才算形成了一条习惯航线」。太小的 MinPts 会把偶发的绕行也识别成簇,太大会把使用频率低的支线航线全部判成噪声。

找 eps 的标准做法是 k-距离曲线:对每条轨迹,取它到第 k 近邻的距离(k 取 MinPts-1),把所有轨迹的这个值从大到小排序画图,找拐点。拐点之前是簇内距离,拐点之后是簇间距离,eps 取在拐点处。

k = minPts - 1; kdist = zeros(N, 1); for i = 1:N s = sort(D(i, :)); kdist(i) = s(k + 1); % s(1) 是自身距离 0 end kdist = sort(kdist, 'descend'); plot(kdist); grid on; xlabel('轨迹按 k-距离降序'); ylabel('第 k 近邻距离 (m)');

拐点不明显的时候,说明数据的密度是连续变化的,不存在一个「天然」的 eps。这种情况下 DBSCAN 的硬边界假设本身就不成立,要么换 HDBSCAN 这类层次化方法,要么承认结果对参数敏感,做多组参数下的稳定性分析。

4.2 核心点、边界点与噪声点:一条船什么时候被判为噪声

DBSCAN 的三分类在轨迹场景下有一套很实用的对应关系,理解这个对应关系比背算法定义有用得多。

核心点:邻域内轨迹数 ≥ MinPts,代表一条主干航线上的典型轨迹。它的存在说明这条路线被足够多的船重复走过,是稳定的交通流。

边界点:本身不是核心点,但落在某个核心点的邻域内,代表走主干航线但略有偏差的轨迹。比如进出港时选了不同的引航路径,或者为了避让临时偏了一点。

噪声点:既不是核心点,也不在任何核心点的邻域内,代表孤立行为。掉头、原地抛锚、异常作业、GPS 故障,都会落到这一类。

这里有个必须说清楚的机制细节:DBSCAN 的簇不是简单的连通分量。如果只是连通分量,那所有互相可达的轨迹都该归为一类,边界点会把两个本来分离的核心簇桥接起来。DBSCAN 的实际规则是「核心点之间连通 + 边界点单向吸附」,边界点只归属到第一个发现它的核心点所在簇,它自己不参与扩张。这个不对称的设计是为了防止单条边界轨迹把两个簇粘在一起,代价是边界点的归属依赖于遍历顺序,结果不唯一。

代码实现上,因为距离矩阵已经预计算好了,邻域查询退化成一个纯粹的索引操作,不需要在原始特征空间里做 radius search:

function [label, isCore] = dbscanDist(D, eps, minPts) % D: N x N 对称距离矩阵,对角线为 0 % 返回值 label: 0 未分类 / -1 噪声 / 正整数 簇编号 N = size(D, 1); label = zeros(N, 1); isCore = false(N, 1); nb = cell(N, 1); for i = 1:N nb{i} = find(D(i, :) <= eps); % 邻域,含自身 isCore(i) = numel(nb{i}) >= minPts; end cid = 0; for i = 1:N if label(i) ~= 0, continue; end if ~isCore(i) label(i) = -1; % 暂定噪声,可能被后续吸附 continue; end cid = cid + 1; label(i) = cid; seed = nb{i}; k = 1; while k <= numel(seed) j = seed(k); if label(j) == -1 label(j) = cid; % 边界点吸附,不扩张 elseif label(j) == 0 label(j) = cid; if isCore(j) seed = [seed, nb{j}]; % 只有核心点才扩张 end end k = k + 1; end end end

seed只增不减,靠label(j) == 0这个判断保证每个点最多进入一次,不会死循环。label(j) == -1的分支是边界点吸附,注意这里没有再做isCore判断,也就是说被吸附的噪声点不会进一步扩张邻域,这是 DBSCAN 和「连通分量」最本质的区别。

4.3 距离矩阵与原始特征空间:为什么这里不需要空间索引

主流的 DBSCAN 实现(包括各种点云库)都会强调 KD-tree、Ball-tree 这类空间索引,用来加速邻域查询。但在这套流程里,空间索引完全用不上,原因值得讲清楚。

空间索引能work的前提是:样本点在一个低维欧氏空间里,且距离函数满足三角不等式,可以用「到查询点的距离 + 索引节点的包围半径 < eps」来剪枝。轨迹距离既不满足三角不等式(截断版本),样本也不是低维向量(每条轨迹是变长的点序列),两个前提都不成立。

所以实际的架构必然是两段式:先把 N 条轨迹压缩成一个 N×N 的距离矩阵,这一步是离线的、可并行的、代价高昂的;然后 DBSCAN 在这个矩阵上做纯索引操作,代价是 O(N²) 次比较,相对便宜。这个分界点很重要,它意味着参数的调整(eps、MinPts)可以反复做,不用重算距离矩阵;但如果改了距离函数的参数(K、权重),整个矩阵必须重算。

工程上常见的做法是把距离矩阵存盘,连同距离函数的参数一起存成元数据,下次调参直接加载。5000 条轨迹的 double 矩阵是 200 MB,压缩存成单精度 float32 减半,完全可接受。这个习惯能省下大量重复计算的时间。

5. 避坑与排查:跑出来的簇为什么不像船走的路线

5.1 数据预处理阶段的三个高频错误

现象一:eps 设成 0.01 之后一条簇都聚不出来,改成 0.1 又全部糊成一团。

原因几乎可以确定是经纬度直接当平面坐标用了。度不是长度单位,1 度纬度约 111 公里,1 度经度在中纬度地区约 85 公里,两者还不相等。用度算出来的 Hausdorff 距离量级在 10⁻³ 到 10⁻² 之间,eps 自然也在这个量级,整个参数空间被压缩得没有任何调节余地。

解决办法是先把经纬度投影到米制平面。小范围水域用等距圆柱投影就够了:以数据中心的纬度为参考,x = (lon - lon0) × 111320 × cos(lat0),y = (lat - lat0) × 110540。大范围或者高纬度数据用 UTM 分带投影。这一步做完,所有距离参数(eps、K、粗筛下界)才有一致的米制含义。

现象二:短轨迹和长轨迹之间的距离总是异常大,短轨迹全被判成噪声。

这是单向 Hausdorff 的点数偏差在作祟,前面分析过,max 运算在点数多的一方有更多取极值的机会。即使两条轨迹形状完全一致,只是采样密度不同,距离也会系统性偏大。

解法有两个层次。轻量做法是在距离计算前统一抽稀到相近的点数,用 Douglas-Peucker 或者等弧长重采样,让每条轨迹都变成 30 个点。彻底做法是在距离里加一个长度惩罚项,把「点数差异」显式地建模进去,而不是让它隐式地污染几何项。我一般倾向前者,因为抽稀本身还能顺带解决计算量问题。

现象三:同一份数据跑两次,簇的编号和边界点的归属不一样。

原因在 4.2 节讲过,边界点的单向吸附依赖遍历顺序。如果数据里有大量落在两个簇之间的过渡轨迹,结果就会不稳定。

如果业务上必须可复现,最直接的做法是固定遍历顺序,比如按轨迹的起始时间戳排序后再跑,这样至少同一份数据每次结果一致。更稳妥的做法是只信任核心点,把边界点的归属标记为「不确定」,在后续的航迹预测里不对这些轨迹做强约束。还有一个思路是把边界点的归属改成「按距离加权分配给多个簇」,这已经偏离了标准 DBSCAN,但实际业务里往往比硬分配更有用。

5.2 参数与结果验证阶段的三个排查方向

现象四:eps 稍微动一点,簇的数量从 3 个跳到 20 个。

先别怀疑算法,去看距离分布的直方图。把所有 D(i,j)(i<j)画成直方图,如果分布是单峰连续的,没有任何低谷,说明这份数据不存在天然的密度分界,DBSCAN 的硬阈值假设不成立。

这种情况通常出现在交通流连续变化的水域,比如从主航道到支流的分流区。可行的处理是把研究区域按地理范围切开,分段聚类,让每一段内部的密度相对均匀;或者换用不依赖全局阈值的层次聚类做交叉验证,看两种方法在哪些轨迹上判断一致,不一致的部分单独人工核查。

现象五:一条明显异常的轨迹把两个本来分离的簇连成了一个。

这是截断常数 K 设太大的典型症状。异常轨迹在空间上横跨两个簇,如果 K 大到足以覆盖这段跨度,它就成了两个簇之间的「桥」,DBSCAN 会沿着这条桥把两边连通。

排查方法是先做轨迹级的质量过滤,把速度超过阈值(比如 40 节)、相邻点间距超过物理可能(比如 1 分钟移动 10 公里)、点数少于下限的轨迹剔掉,再进距离矩阵。如果异常轨迹是真实的业务行为(比如搜救船的往复搜索),那就必须靠调小 K 或者引入航向项来区分,不能靠过滤。顺序很重要:先清洗再聚类,不要指望聚类算法自己识别脏数据。

现象六:簇的形状看起来合理,但和已知航道对不上。

这时候要去验证距离矩阵本身,而不是继续调 DBSCAN 参数。取几条已知同航线的轨迹,手算它们的距离,看是否落在 eps 以内;再取几条已知不同航线的,看是否落在 eps 以外。这个手算验证花不了十分钟,但能立刻定位问题是在距离函数还是在聚类参数。

还有一个很有用的可视化手段:把距离矩阵按簇编号重排后画成热力图。理想情况下应该看到明显的块对角结构,块内颜色浅(距离小)、块间颜色深。如果块内出现深色斑点,说明簇内有离群轨迹没被识别;如果块间有大片浅色区域,说明两个簇的距离其实很近,可能是被某个参数人为分开的。

6. 设计取舍:改进 Hausdorff 的边界在哪里

把几种轨迹距离放在一起对比,取舍会清楚很多。下表的结论来自实际跑过几份内河和近海数据后的体感,不是理论推导:

距离时序信息对采样不同步离群点鲁棒三角不等式单次计算量
经典 Hausdorff丢失鲁棒极差满足O(nm)
截断 Hausdorff丢失鲁棒好(受 K 约束)不满足O(nm)
分位 Hausdorff丢失鲁棒好不满足O(nm log n)
DTW保留需要归一化中等不满足O(nm)
Fréchet部分保留敏感差满足O(nm)

最关键的一行是「时序信息」。Hausdorff 家族全部丢失时序,它只看两条轨迹的点的集合,不看点的先后顺序,所以一条船正着走和倒着走,Hausdorff 距离完全相同。在船舶场景里这通常不是问题,因为航向项已经把方向信息补回来了;但如果你的业务需要区分「同一条路不同速度剖面」,Hausdorff 就力不从心了。

那为什么不直接用 DTW?因为 DTW 的对齐机制在这里反而会成为负担。AIS 的采样间隔从 2 秒到 3 分钟不等,DTW 会强行把不同时刻的点配成一对,制造出原始数据里不存在的对应关系。两条船在同一条航道上,一条匀速一条走走停停,DTW 会把停船时积累的那一堆重复点摊薄到整条轨迹上,得到一个看似很小的距离值,反而掩盖了速度剖面的真实差异。Hausdorff 不做这种强行对齐,它对采样不同步的鲁棒是「结构性的鲁棒」,不是「靠算法补出来的鲁棒」。

真正的取舍点在 K 和权重这两组参数上。K 定得小,所有轨迹在截断之后都长得差不多,簇数塌缩,业务上等于什么都没分出来;K 定得大,退化成经典 Hausdorff,单点跳变重新主导结果。这个窗口在数据质量差的时候会非常窄,窄到没有实用的调节空间。血泪经验是:如果 K 的可用窗口小于一个数量级,先回去修数据,不要试图用参数掩盖数据问题。

权重那部分更微妙。每加一个特征项,就是在距离里注入一条先验假设,权重的大小直接反映了你有多相信这条假设。这件事没有客观最优解,只有和数据分布匹配不匹配。我见过把航向权重调到 0.8 的方案,在某个特定水域效果极好,换一个水域直接失效,因为那个水域的航道本身就在连续转弯,航向差根本不是区分上下行的有效特征。

验证的方法建议用已知航道做 ground truth。分道通航制的水域天然有上下行两条分离的航道,把聚类结果和航道归属做交叉表,算一下簇的纯度。纯度低于 0.8 的时候,先别调 DBSCAN,回去看距离矩阵里同航道轨迹的距离分布和跨航道轨迹的距离分布有没有重叠,重叠区域有多大。这个诊断能直接告诉你距离函数的上限在哪里,比反复试参数高效得多。

从那以后我每次跑航迹聚类之前,都强制先做一件事:把距离矩阵的上三角拉平,画一张直方图,再画一张按已知航道分组的箱线图。这两张图花五分钟,但能提前判断这次聚类到底有没有希望跑出有意义的结果,省下来的调参时间是以小时计的。希望这个习惯也能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询