
简介基于假最近邻FNN算法的Matlab完整源码包面向从事混沌时间序列分析、神经信号处理及非线性动力学建模的研究者与工程师。该算法通过计算最小嵌入维数量化时间序列动态系统的复杂度可用于评估神经活动的复杂性。包内包含Henon地图与合成电极信号两个典型实验运行FNN_HenonMap.m可验证二维混沌系统的最小嵌入维数为2并观察时间点数量对高维估计的影响运行FNN_Electrode.m可对比2kHz与10kHz采样率信号的嵌入维数差异理解下采样对信息量的影响。压缩包内共53个文件以29个Matlab脚本为主附17个dat数据文件、5个csv数据表及1个xls表格整体仅77KB结构精简、可直接运行。目前已有274人学习下载这套源码适合需要快速上手FNN方法并开展时间序列复杂度分析的科研与工程场景。1. FNN 为什么能确定时间序列的最小嵌入维数如果你手里只有一段单变量时间序列比如 Lorenz 系统的 x 分量、一台设备的振动信号或者一段脑电记录想对它的动态行为做可靠分析或预测第一个绕不过去的问题就是系统的状态到底由几个变量决定这个数目就是最小嵌入维数也叫重构维数。自相关函数和功率谱只能描述信号的频率成分与线性相关性给不出自由度信息而假最近邻False Nearest NeighborsFNN方法从状态空间重构出发用“邻居关系在高维空间中是否被拆散”来回答这个问题。FNN 的做法很直观在m维相空间里彼此靠近的两个点如果把维度扩展到m1后距离急剧增大说明它们在m维里的靠近只是因为维度不足而造成的假象这样的邻居就叫假最近邻。把假邻居比例随m变化的曲线画出来第一个让比例降到接近零的m就是最小嵌入维数。这个结论直接决定后续用多大的状态向量做预测、用多长的滑窗喂给 LSTM 这类模型也决定了相空间重构是否真正还原了系统的动力学结构。这里以 Matlab 为例从 FNN 的原理讲到完整可运行的源码再用混沌、周期和噪声三类数据把判断准则说透。2. FNN 判据原理相空间重构、距离比与扩展半径2.1 Takens 嵌入定理与最小嵌入维数对观测序列x(i)选定延迟时间tau后可以构造m维相空间中的状态向量X_m(i) [x(i), x(itau), ..., x(i(m-1)*tau)]Takens 嵌入定理保证当m大于系统吸引子维数的两倍时重构的相空间与原系统的状态空间在微分同胚意义下等价动力学结构不丢失。但实际中我们并不知道吸引子维数只能从小到大逐个尝试m。当m太小时吸引子会出现折叠原本不相邻的点被挤压到一起表现为大量假近邻当m增大到足以展开吸引子时假近邻比例会快速下降。这里要区分一个概念最小嵌入维数不等于系统变量个数。Lorenz 系统的状态空间是 3 维但只取其中一个分量做相空间重构FNN 给出的最小嵌入维数通常也是 3。这说明一段单变量序列足以重建原系统的大部分动力学信息这正是 FNN 方法在时间序列分析里的价值所在。2.2 FNN 的两个判据距离比与扩展半径设第i个相点在m维空间中的最近邻是第j个相点两者在m维空间的距离记为d_m(i, j)。扩展到m1维时新增一个坐标分量x(i m*tau)于是d_m1(i, j) sqrt(d_m(i, j)^2 (x(i m*tau) - x(j m*tau))^2)FNN 的第一个判据是距离比判据若sqrt(d_m1^2 - d_m^2) / d_m Rtol说明新增维度带来的坐标差相对旧距离过大这两个点在m维中的接近很可能是折叠造成的记一次假近邻。这个判据对旧距离非常小的点过于敏感因此需要第二个判据兜底。第二个判据是扩展半径判据若d_m1 / sigma Atol其中sigma是原始时间序列的标准差则认为扩展后的距离已经超出整个吸引子的延展尺度同样记为假近邻。两个判据一个看相对变化一个看绝对尺度合并时最常用的是“或”逻辑即至少命中一条就判定为假近邻这也是 Kennel 原始论文和 TSTOOL 等经典实现的通用合并方式。2.3 Rtol 和 Atol 的取值常见默认值与调整方向参数作用常用默认值何时需要调整Rtol抑制因旧距离过小而产生的误判10噪声偏大时调到 15 以上Atol防止距离比判据漏掉绝对离群点2数据尺度波动大时配合sigma校准tau相空间相邻坐标的时间间隔1或由互信息法确定tau过小会导致 FNN 比例降不下来Rtol为什么不取 2 或 3因为d_m是最近邻距离在噪声存在时两个噪声点本身可能靠得很近比值很容易穿过小阈值产生大量误报。Rtol 10是多年实践验证的经验值对噪声相对稳健。Atol 2意味着扩展后的距离超过两个标准差就认为是离群点。sigma在实现中用整段序列的标准差计算如果数据非平稳更稳妥的做法是用相空间中各点离吸引子中心的平均距离代替避免趋势项把尺度拉大。3. Matlab 实现 FNN 的核心函数与完整调用代码3.1 一个可直接运行的 fnn_embedding 函数function [fnn_ratio, d_fnn, a_fnn] fnn_embedding(x, tau, mmax, Rtol, Atol) % FNN_EMBEDDING 用假最近邻法寻找最小嵌入维数 % 输入 % x : 一维时间序列列向量 % tau : 延迟时间正整数 % mmax : 最大考察的嵌入维数 % Rtol : 距离比判据阈值典型值 10 % Atol : 扩展半径判据阈值典型值 2 % 输出 % fnn_ratio : 两个判据取“或”后的假邻居比例长度 mmax % d_fnn : 仅距离比判据的命中比例 % a_fnn : 仅扩展半径判据的命中比例 x x(:); N length(x); sigma std(x); if sigma 0 || N 30 error(输入序列标准差为0或长度不足); end fnn_ratio zeros(mmax, 1); d_fnn zeros(mmax, 1); a_fnn zeros(mmax, 1); for m 1:mmax M N - (m - 1) * tau; % m 维相点总数 Mvalid M - tau; % 能进一步扩展到 m1 维的相点数 if Mvalid 2 fnn_ratio(m) 1; continue; end X zeros(M, m); % 相空间矩阵每行是一个 m 维状态 for k 1:m X(:, k) x(1 (k-1)*tau : M (k-1)*tau); end count_d 0; % 判据一命中数 count_a 0; % 判据二命中数 count_u 0; % 并集命中数 for i 1:Mvalid ref X(i, :); best_j -1; best_dist inf; for j 1:Mvalid if j i continue; end dist sqrt(sum((ref - X(j, :)).^2)); if dist best_dist best_dist dist; best_j j; end end % 构造 m1 维距离 d_m best_dist; diff_new x(i m*tau) - x(best_j m*tau); d_m_plus sqrt(d_m^2 diff_new^2); % 距离比判据d_m 为 0 时直接视为假邻居 if d_m 0 ratio inf; else ratio abs(diff_new) / d_m; end hit_d ratio Rtol; hit_a d_m_plus Atol * sigma; if hit_d count_d count_d 1; end if hit_a count_a count_a 1; end if hit_d || hit_a count_u count_u 1; end end d_fnn(m) count_d / Mvalid; a_fnn(m) count_a / Mvalid; fnn_ratio(m) count_u / Mvalid; end end代码的核心逻辑是两层循环外层遍历嵌入维数m内层对每个相点找最近邻并判定是否假邻居。构建相空间矩阵时M N - (m-1)*tau是有效的相点数量而Mvalid M - tau进一步限制了能参与扩展判定的点因为要把当前点扩展到m1维时需要访问序列中i m*tau位置的数据索引不能越界。这里把距离比判据写成了ratio abs(diff_new) / d_m它与理论公式sqrt(d_m1^2 - d_m^2) / d_m完全等价因为d_m1^2 d_m^2 diff_new^2。当最近邻距离正好为 0 时说明序列中存在完全相同的状态此时直接判为假邻居避免除零错误。返回的三个比例中fnn_ratio是并集命中率d_fnn和a_fnn可以单独观察两个判据各自的表现。如果数据量很大内层最近邻循环是性能瓶颈可以把内层循环替换成knnsearch(X, X, K, 2)再排除自身匹配项结果与手写循环一致。3.2 主脚本生成 Lorenz 数据并调用 FNN% demo_fnn.m Lorenz 系统的 x 分量作为测试数据 sigma 10; rho 28; beta 8/3; lorenz (t, y) [sigma*(y(2)-y(1)); y(1)*(rho - y(3)) - y(2); y(1)*y(2) - beta*y(3)]; [t, y] ode45(lorenz, [0 100], [1; 1; 1]); y y(t 20, :); % 去掉瞬态段取稳态吸引子 x y(1:2:end, 1); % 取 x 分量并抽稀 x x(1:2000); % 用自相关法估计 tau取归一化自相关首次低于 1/e 的时刻 xc x - mean(x); ac xcorr(xc, 200, normalized); ac ac(201:end); tau find(ac 1/exp(1), 1, first); if isempty(tau) tau 1; end fprintf(自相关法估计 tau %d\n, tau); % 调用 FNN 函数 [fnn, d_only, a_only] fnn_embedding(x, tau, 10, 10, 2); for m 1:10 fprintf(m%2d fnn%.4f d_fnn%.4f a_fnn%.4f\n, ... m, fnn(m), d_only(m), a_only(m)); end % 保存数据方便后续脚本复用 save(lorenz_x.mat, x, tau);主脚本先用ode45积分 Lorenz 方程丢弃前 20 秒的瞬态再对序列做抽稀降低相邻样本的相关性避免tau完全被采样频率主导。自相关法估计tau的代码不依赖任何工具箱xcorr计算归一化自相关取滞后为正的半边找到首次低于1/e的位置作为tau。运行后可以看到fnn在某个m处跌落到接近 0这个m就是 Lorenz x 分量的最小嵌入维数。保存的lorenz_x.mat就是后续对比实验和二维网格扫描的数据源。3.3 输入参数与返回值的选择要点参数推荐范围说明N序列长度至少 500最好 2000 以上样本太少时最近邻统计不稳定tau1 到 20由自相关或互信息法确定tau太大破坏动力学邻域结构mmax8 到 15比期望维数大 2 到 3 倍即可Rtol10噪声大时 15判定折叠假象的敏感度Atol2 到 3随数据标准差自动缩放使用非平稳数据比如带趋势的温度记录或股票价格需要先做一阶差分再去跑 FNN。趋势成分会把标准差sigma拉大导致扩展半径判据失效FNN 曲线整体偏高找不到明显的谷底。4. FNN 实战Lorenz、周期信号与噪声数据的最小嵌入维数分析4.1 三种典型数据集的 FNN 结果对比% compare_demo.m 需要先运行 demo_fnn.m 生成 lorenz_x.mat rng(1); N 2000; t (0:N-1); x_sin sin(2*pi*0.01*t); % 周期信号 x_rand randn(N, 1); % 白噪声 load(lorenz_x.mat, x); % Lorenz x 分量 y_list {x_sin, x_rand, x}; names {sin, noise, lorenz}; for k 1:3 xx y_list{k}; xx xx - mean(xx); ac xcorr(xx, 100, normalized); ac ac(101:end); tau find(ac 1/exp(1), 1, first); if isempty(tau) tau 1; end [f, ~, ~] fnn_embedding(xx, tau, 10, 10, 2); fprintf(%8s tau%2d fnn , names{k}, tau); fprintf( %6.3f, f); fprintf(\n); end三类数据的典型结果如下表所示具体数值会随抽稀方式和积分参数略有变化数据tau自相关法首次 fnn1% 的 m结论正弦信号自相关衰减极慢常落到默认值 12极限环2 维Lorenz x6 到 12 附近3 到 43 维混沌吸引子白噪声1无明显谷底不存在有限维嵌入正弦信号的自相关不会衰减到1/e以下所以代码中tau会走默认值 1这一点提醒我们对周期信号自相关法并不适用互信息法才是更稳妥的tau估计方式。白噪声因为本身没有确定性结构FNN 曲线虽然会随着维度增加缓慢下降但不会出现明显的谷底这说明单纯扩大嵌入维数并不能消除伪邻域序列不适合做相空间重构。4.2 延迟时间 tau 与嵌入维数 m 的耦合影响% 扫描 tau观察最小 FNN 值及其对应 m 的变化 for tau_try [1 2 3 5 10 15] [f, ~, ~] fnn_embedding(x, tau_try, 10, 10, 2); [vmin, idx] min(f); fprintf(tau%2d 最小fnn%.4f 在m%d\n, tau_try, vmin, idx); end当tau取 1 时原始序列相邻点高度相关最近邻常常就是时间上相邻的点这些点在扩展维度后依然接近容易被误判为真邻居导致 FNN 整体偏低最小嵌入维数也被低估。反过来tau取得过大时相空间中距离接近的点在时间上已经离得很远最近邻来自吸引子上不相干的支叶FNN 比例会一直维持在高位找不到明显的下降点。经验约束是让嵌入窗宽(m-1)*tau落在信号特征时间尺度的 0.5 到 2 倍之间。实际项目中我一般先用互信息法确定tau的候选范围再跑这个扫描循环观察最小 FNN 对应的m是否稳定。如果m随tau大幅跳动说明数据质量有问题或者需要先做去趋势和滤波。4.3 FNN 误判的常见场景与排查顺序FNN 比例在高维一直不降先查tau是否过大再查序列是否包含强趋势或间歇性尖峰必要时做差分或去趋势。m1时就几乎没有假邻居通常是采样率过高相邻样本过于相关或者序列本身是白噪声。先看自相关是否快速衰减到 0若是则 FNN 结论不可靠。某个m之后 FNN 出现反弹噪声能量会随维度增加重新分配到新坐标上反弹是噪声存在的标志。此时取第一个最小值而不是等 FNN 降到 0。程序报索引越界多半是tau与mmax的乘积接近序列长度N导致Mvalid小于 2。把mmax调小或缩短tau。提示当N超过 5000 时双层循环的最近邻搜索会明显变慢。常见做法是把内层查找替换为knnsearch(X, X, K, 2)然后排除与自身重合的索引判定逻辑完全不变。5. FNN 参数扫描验证以及嵌入维数的落地技巧5.1 网格扫描同时确定 tau 与 m% grid_scan.m tau_list 1:8; m_list 1:10; FNN_MAP zeros(length(tau_list), length(m_list)); for a 1:length(tau_list) [FNN_MAP(a, :), ~, ~] fnn_embedding(x, tau_list(a), 10, 10, 2); end imagesc(m_list, tau_list, FNN_MAP); xlabel(嵌入维数 m); ylabel(延迟时间 tau); colorbar; caxis([0 1]);这段代码生成一张tau对m的二维热图深色区域表示 FNN 比例低。看热图时沿着m轴观察如果在多个tau下都有一段深色走廊落在同一个m附近这个m就是稳健的最小嵌入维数。单条曲线可能受tau选择影响热图把tau和m的耦合关系一次呈现出来能有效避免陷入局部最优。5.2 把最小嵌入维数接到 LSTM 与相空间预测中FNN 给出的m最直接的应用是构造m维状态向量做局部线性预测或 KNN 预测。在深度学习工作流里m也可以作为 LSTM、GRU 输入窗口长度的先验值。比如用 Python 做 LSTM 时间序列预测时把 lookback 从拍脑袋的经验值改为由 FNN 估计出的m可以明显缩小超参数搜索范围。但要注意LSTM 的输入步长通常还需要覆盖数据的周期成分FNN 的m描述的是自由度而不是周期性两者并不冲突。实际使用中我一般用m作为窗口下界再用自相关或周期图确定上界。完整跑通这套流程的方法是先运行demo_fnn.m拿到lorenz_x.mat再运行compare_demo.m对比三类数据最后用grid_scan.m做网格验证把x和tau一起保存为 mat 文件交给下游建模脚本。在这之后同样一套fnn_embedding函数可以直接换成实测振动信号或生理信号只需要重新确定tau并检查 FNN 曲线是否存在可辨识的谷底。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。