
简介这套基于MATLAB实现的动态模式分解DMD资源适合流体力学、信号处理和结构健康监测等领域的科研人员与研究生。资源共3个文件包含PDF格式的算法说明文档、Markdown格式的阅读笔记及配套的MATLAB演示脚本压缩包整体仅146KB轻量易用。动态模式分解通过奇异值分解提取复杂系统的主要演化模式可用于重构高维时间序列并预测未来状态演示脚本完整展示了从数据构造矩阵、SVD谱分解到DMD频率与增益计算的关键流程配合文档可快速上手并可按需延伸至eDMD、cDMD等变体。已有497人学习下载对希望理解低维动态建模或开展预测分析的读者具有实用参考价值。1. 拿到一份 Dynamic Mode Decomposition (DMD).zip你要解决的问题从来不是解压很多做流场分析、振动监测或时序预测的工程师电脑里都躺过一份名为 Dynamic Mode Decomposition (DMD).zip 的文件。解压之后通常是一堆 .py 或 .m 脚本、几个 .mat 或 .csv 数据文件外加一份写得并不完整的 README。如果你只是把它当成一个“解压就能用的工具箱”大概率会在跑通第一个案例之后卡在真实数据上然后骂一句“这算法怎么这么玄学”。这个压缩包指向的技术方向是动态模态分解Dynamic Mode Decomposition。它和 FFT、POD本征正交分解最大的不同在于FFT 告诉你信号里有哪些频率POD 告诉你哪些空间结构能量最大而 DMD 直接给你一组随着时间演化的“模态”——每个模态有自己的频率、增长率衰减率和空间形态。这意味着你不仅能描述过去还能重构序列、预测短期未来甚至从数据里反推出近似线性算子。这正是流场涡街分析、结构振动识别、金融时序短临预测这些场景里DMD 比传统分解方法更值钱的地方。这篇文章不会去复述论文公式推导而是按一线使用的思路拆解这份 zip 里的代码逻辑是什么、数据该怎么组织、r 和 dt 这些参数怎么定、哪些环节会让结果翻车。内容适合两类人一是刚接触 DMD、想拿现成代码跑通第一个案例的初学者二是已经在用 POD 或 FFT、想评估 DMD 值不值得引入到自研工具链里的工程师。读完你会知道这份 zip 能做什么、不能做什么以及怎么把它改造成自己项目里真正能用的模块。2. 先看懂 DMD 在算什么快照、特征值和模态的三角关系2.1 为什么是 DMD 而不是 FFT 或 POD三种分解的选型边界在处理时间序列或时空场数据时工程师最容易陷入的误区是“什么好跑就跑什么”。FFT 快、稳定、到处有库但它假设信号是平稳的、线性的对非平稳过程只能给出一个时间平均意义上的频谱结果。你拿一段涡街脱落 CFD 数据做 FFT能得到主频但得不到“这个频率对应的涡结构长什么样、在哪里”。POD 解决的是空间问题它把快照矩阵做奇异值分解得到一组按能量排序的空间基函数。但 POD 的模态不包含时间演化信息它的时间系数是事后投影算出来的不是从数据里直接学到一个动力学算子。换句话说POD 擅长压缩不擅长预测。DMD 的视角完全不同。它假设存在一个线性算子 A能把当前时刻的流场状态映射到下一时刻x_{k1} A x_k。虽然真实物理系统几乎都是非线性的但 Koopman 理论告诉我们非线性系统可以在一个无穷维观测空间里被线性算子描述。DMD 就是用一个有限维的近似来逼近这个 Koopman 算子。它输出的每一个模态都自带一个特征值 λλ 的幅值和辐角直接对应这个模态的增长率衰减率和频率。这才有了“既做分解、又做预测”的能力。选型边界很清楚只关心频率分布用 FFT只关心能量集中区域用 POD关心“什么结构在什么频率下以什么速率增长或衰减”以及“能不能外推几步”用 DMD。2.2 一手数据如何组织成 DMD 要的 X 和 X 矩阵拿到 zip 里的代码第一关不是理解算法而是把数据组织成算法能吃的形状。DMD 的核心输入是两个快照矩阵X 和 X有些代码里写成 X1, X2 或 U, V。假设你有 N 个时间步的采集数据每个时间步是一个 m 维向量比如流场所有网格点的压力值排成一列m 可能几万甚至几百万那么 X 由第 1 到第 N-1 个快照组成X 由第 2 到第 N 个快照组成都是 m 行、N-1 列的矩阵。# 假设 data 是二维数组形状为 (m, N) # 每一列是一个时间快照列索引对应时间步 X data[:, :-1] # 第 0 到 N-2 列 X_prime data[:, 1:] # 第 1 到 N-1 列代码逻辑很简单但有个隐藏前提数据必须是等时间间隔采集的。如果你的数据是变步长传感器采集的必须先做插值重采样否则算子 A 的物理含义是错的。另一个容易忽略的问题是矩阵行数 m 远大于列数 N-1 时直接对 X 做 SVD 会非常慢这时候要用随机化 SVD 或先做一次 POD 降维这是后面参数章节的重点。2.3 r 截断、dt 与特征值三个必须想清楚的参数DMD 算法流程本身是固定的对 X 做 SVD取前 r 个奇异值对应的模态做低秩近似构造低维算子求特征值再把特征向量映射回高维空间。这一步所有代码都差不多差别只在 r 怎么选、dt 填多少、以及特征值怎么过滤。r 是截断阶数决定了你保留多少个 DMD 模态。取小了会丢掉弱但重要的模态取大了会把数值噪声一起学进来。工程上常见的做法是看奇异值谱把奇异值按大小排序画出来找拐点。如果数据本身含噪拐点不明显另一招是用硬阈值公式比如 Gavish-Donoho 阈值。多试几个 r观察重构误差曲线误差不再明显下降的位置就是合适的截断点。dt 是快照之间的时间间隔。它只影响特征值到频率的换算不影响特征值本身的计算但影响你对模态物理解读的准确性。如果你把 dt 填成了采样间隔而实际快照之间隔了 10 帧那算出来的圆频率就放大了 10 倍。这里最常见的翻车是把“帧号间隔”和“物理时间间隔”混为一谈。特征值 λ 的幅值决定增长/衰减|λ|1 表示放大发散|λ|1 表示衰减|λ|≈1 对应中性稳定模态。频率由幅角 θ 除以 2π·dt 得到。工程上噪声会让一些虚假模态的 |λ| 偏离 1所以一般会只保留落在单位圆附近一个环带内的模态或者用幅度阈值过滤掉能量太低的模态。3. 把 zip 包里的算法跑起来最小复现与参数落地3.1 解压前先自查伪加密、校验和与目录结构这个标题的实体是一份 zip 压缩包那先从 zip 本身说起。下载来的 DMD 代码包第一步不是急着解压而是校验完整性。命令行下用 unzip -t 做一次完整性测试它会把包内每个文件的 CRC 校验和算一遍输出 OK 表示文件没损坏。这一步能过滤掉八成“跑着跑着报错说数据文件读不了”的坑。unzip -t Dynamic_Mode_Decomposition_DMD.zip如果解压时提示需要密码先别急着找密码。有一种情况叫 zip 伪加密——文件的加密标志位被改过但实际内容没有真正加密。这种文件用常规解压工具会弹窗要密码但用 7-Zip 打开如果右侧“加密”列显示“”而不是“”就是伪加密可以直接解。识别伪加密还有一个技巧用十六进制编辑器看文件头通用标记位 Bit 0 置 1 表示加密但数据描述符里的实际标记若是 0说明只是标记被篡改。处理伪加密很简单7-Zip 直接解或者用 zip -FF 修复一次即可。解压之后先看目录结构一个能用的 DMD 包通常包含三类内容算法主脚本dmd.py 或 dmd.m、示例数据文件.mat、.csv 或 .npy、以及结果可视化脚本。如果只有算法脚本没有数据你需要自己准备快照矩阵如果只有结果没有脚本那你拿到的只是一份跑完的输出得找作者要源码。我习惯拿到手先跑示例数据确认代码在主脚本层面没问题再换成自己的数据。3.2 用 Python 在本地跑通 DMD 的最小代码从快照矩阵到重构不管 zip 里是 MATLAB 还是 Python 实现核心逻辑都是一样的。我以 Python 为例子写一段最小可运行的 DMD 代码用的是 numpy 加 scipy不依赖 pyDMD 库这样你能看清每一步在干什么也方便改造成自己的模块。import numpy as np from scipy.linalg import svd, eig def dmd_snapshot(X, X_prime, rNone): # 对 X 做截断 SVD U, S, Vh svd(X, full_matricesFalse) if r is None: # 保留 90% 能量的截断阶数 cum_energy np.cumsum(S**2) / np.sum(S**2) r np.argmax(cum_energy 0.9) 1 U_r U[:, :r] S_r np.diag(S[:r]) V_r Vh[:r, :].conj().T # 构造低维算子 Atilde求特征值特征向量 Atilde U_r.conj().T X_prime V_r np.linalg.inv(S_r) eigvals, eigvecs_W eig(Atilde) # 把低维特征向量映射回高维空间 eigvecs X_prime V_r np.linalg.inv(S_r) eigvecs_W return eigvals, eigvecs, S, r def dmd_reconstruct(eigvals, eigvecs, X, dt1.0, mode_idxNone): # 计算振幅 b 初态在特征向量基下的坐标 if mode_idx is None: mode_idx np.arange(len(eigvals)) Phi eigvecs[:, mode_idx] lam eigvals[mode_idx] # 最小二乘求解振幅保证第一个时间步的重构误差最小 b np.linalg.lstsq(Phi, X[:, 0], rcondNone)[0] return Phi, lam, b这段代码有两个容易看漏的地方。一是Atilde U_r.T X_prime V_r inv(S_r)的顺序有些实现写成U_r.T X_prime V_r inv(S_r)有些写成U_r.T X_prime V_r / S_r结果一样但 scipy 的 svd 返回VhV 的共轭转置所以 V 要取Vh[:r].conj().T丢了.conj().T只取.T在实数数据里没问题遇到复数模态就全错了。二是振幅 b 的求法常见做法是用第一个快照作为初态做最小二乘也可以用所有时间序列做整体最小二乘前者快但抗噪弱后者稳但代码长一截。3.3 模态频率排序与可视化把结果变成能汇报的图跑完特征值分解只是第一步工程师要的是“哪些模态主导了这个过程”。排序标准有两个方向按能量振幅 |b| · 模态范数和按频率。实际项目里先按能量排序取前几个高能模态分析它们的频率和空间形态低频大尺度结构和高频小尺度涡通常对应不同的物理机理。一段实用的输出逻辑是把特征值画在复平面单位圆上同时把频率画成频谱图哪个模态增益最大一目了然。import matplotlib.pyplot as plt # 幅度和频率计算 omegas np.angle(lam) / (2 * np.pi * dt) # 物理频率 growth np.abs(lam) # 画特征值位置辅助判断稳定性 fig, ax plt.subplots(figsize(6, 6)) theta np.linspace(0, 2 * np.pi, 200) ax.plot(np.cos(theta), np.sin(theta), k--, linewidth0.8) ax.scatter(lam.real, lam.imag, cnp.abs(b), cmapviridis, s40) ax.set_xlabel(Re(λ)) ax.set_ylabel(Im(λ)) ax.set_aspect(equal) plt.show()这段可视化代码的本质是把特征值的幅角映射为频率、幅值映射为增长率颜色映射为能量。注意lam是复数数组.real和.imag分别取实部和虚部。画图之前建议先过滤掉|λ|与 1 偏差过大的模态否则图里会有一堆发散或快速衰减的散点干扰判断。频率换算里 dt 的物理单位要写清楚秒、小时还是采样帧直接影响你对结果的理解。4. 参数怎么设才不翻车r、dt、快照数的工程取值4.1 奇异值截断 r能量占比与硬阈值两种选法r 是 DMD 里唯一需要人工拍板的超参数拍错了直接影响模态质量。最稳妥的做法是画出奇异值谱看前几十个奇异值的下降趋势。典型含噪数据的前几个奇异值下降很快然后进入一个缓慢衰减的“平台区”平台区的起点就是噪声底的信号r 取在拐点附近即可。量化一点可以用能量占比。把奇异值平方归一化求和取累计能量达到 90% 或 95% 的截断点。我一般先试 90%如果重构误差偏大再提到 95%但极少用超过 99%——那等于没做降噪噪声模态会混进来。Gavish-Donoho 硬阈值也值得一试它根据矩阵尺寸和奇异值分布给出理论最优阈值公式在 scipy 里几行就能实现。我自己的习惯是两种都看r 取两者之间数值较小者因为 DMD 模态数量宁少勿多多一个模态就多一分伪模态的风险。还有个工程技巧如果你压缩包里的代码提供了dmd_modes函数或类似接口看看它接受的 r 是整数还是比例值。有些实现写成r0.9表示能量占比自动截断有些要求传整数。传错类型不会报错但结果会很怪这是“黑匣子”式代码包的典型问题。4.2 dt 不是采样间隔映射矩阵 A 的时间步含义zip 包里如果有 README多半会写一句“dt 是采样时间间隔”。但这一步埋着大坑。DMD 的 dt 指的是 X 相邻两列之间的时间差如果你的原始采样是 1000 Hz但你每隔 10 帧取一个快照那 dt 应该是 0.01 秒而不是 0.001 秒。很多工程师在预处理时做了降采样或滑动窗口却忘了同步修改 dt最终频率全部偏了一个倍数。验证 dt 是否设对的方法很简单用你已知的主频做参照。比如这是圆柱绕流,Strouhal 数对应的涡脱频率大约是 5 Hz跑完 DMD 看频谱里能量最高的模态频率是不是接近 5 Hz。差一个数量级多半是 dt 错了差 0.3 Hz 左右可能是 r 取太大混入了噪声模态或者数据本身有频率泄漏。如果你做的是差分框 DMD把 X 换成差分 X_prime - Xdt 的含义会变频率换算公式也要相应调整。这部分 zip 包里不一定有需要你自己看代码里特征值转频率那行确认它用的 2π·dt 还是 2π·dt 加某个修正项。4.3 快照数多少够用欠采样与过采样的边界快照数 N 决定了 X 矩阵的列数。N 太小的后果是 SVD 结果不稳定一个噪声点就能扭曲主要模态N 太大的问题不是算不动DMD 的复杂度是 O(m·N·r)m 大才要命而是时间跨度太长线性假设失效——早期和晚期的动力学特征不一样用一个线性算子硬拟合得到的模态是“平均动力学”哪个阶段都没描述好。经验值对周期性较强的流场或振动信号N 至少要覆盖 5 到 10 个主周期对非周期的衰减过程N 要覆盖主要能量衰减到 10% 以下的时间范围。具体做法是先把数据按不同 N 切段跑 DMD比较模态频率是否稳定。频率随 N 变化超过 5%说明你的时间窗不够代表系统动力学。这时候截取一段动力学相对一致的子序列比硬塞进全部数据靠谱得多。4.4 三种 DMD 变体怎么选Exact、Optimized、Sparsezip 包如果比较完整里面可能同时出现几种 DMD 变体。最基础的是 Exact DMD也就是标准 SVD 流程速度快、易实现适合数据噪声不高、线性度尚可的系统。如果数据噪声明显用 Optimized DMD 更合适它直接对重构误差做整体最小二乘优化模态频率更准但迭代求解慢数据量大时会想砸电脑。Sparse DMD 是另一路它给模态系数加了稀疏约束让大部分振幅归零只留少数几个主导模态。这对解读流场特别有用标准 DMD 可能给出 20 个模态但能量集中在 3 个里面Sparse DMD 直接把这 3 个挑出来省去人工筛选。代价是要调稀疏惩罚系数这个系数跟数据尺度强相关换一组数据就得重新调。我的建议是第一遍用 Exact DMD 跑通全流程看频率谱里哪些模态稳定出现确定主模态后想发论文或做高精度预测再试 Optimized数据是纯周期叠加、噪声又大Sparse DMD 的“少模态可解释性”值得投资。没必要一上来就上重武器DMD 的多数工程问题Exact 版本已经能回答七八成了。5. 避坑我在 DMD 上踩过的五个典型问题5.1 伪模态泛滥特征值成对出现且非常接近单位圆现象跑出来的模态数量很多频率谱上一片毛刺看不出明显主峰重构误差不高但每个模态单独看都没有物理意义。原因噪声被 DMD 当成了真信号。每个噪声扰动会形成一对共轭模态它们的特征值幅角对称分布在单位圆附近频率随机、能量很低。这是 DMD 的数学性质不是代码 bug。解决把 r 往下压优先保留奇异值谱拐点之前的模态再做一次模态过滤剔除 |λ| 偏离 1 超过某个阈值比如 0.02的模态最后按振幅排序只取前 3 到 5 个模态做物理分析。这一套流程下来伪模态基本能被清干净。5.2 特征值落在单位圆外导致重构发散现象重构未来几步时曲线很快增长到数值溢出出现 inf 或 NaN但短时间窗口内的重构是正常的。原因噪声数据导致某些特征值 |λ| 略大于 1对应模态随时间指数放大做外推时几步就爆了。这是 DMD 从数据拟合线性算子时最经典的不稳定问题。解决方案有两个。一是做特征值约束把 |λ|1 的模态幅值投影回单位圆内这等于承认真实系统是稳定的只保留增长趋势的相位信息二是用“提前截断”的策略只做短时预测比如预测步数不超过快照数 N 的 1/10不去追长期演化。若需要长期预测改用 Optimized DMD 往往能改善。5.3 r 选太小把关键模态截掉了现象重构的总体误差不高但频谱里缺少了你一开始就观察到的主频或者低阶重构与原始数据在某个局部区域明显偏离。原因这个主频对应的模态能量占比不高但在动力学上很重要。用能量占比 90% 截断时这个弱模态被丢掉了但它在某个时间窗内的相位作用不可替代。解决不只看累计能量还要看奇异值谱里有没有“孤立的小峰”。如果有把它对应的阶数纳入 r。通用做法是把 r 从 1 到 min(m, N-1) 扫一遍对比每个 r 下重构结果与原始数据的相关性和频谱峰值位置画出“r-主频误差”曲线拐点处通常就是合适截断点。5.4 时间步不均匀导致算子 A 失真现象跑出来的模态频率明显偏小或偏大重构整体趋势对但细节相位总是对不上更换插值方法后结果变了。原因数据采集不是等间隔的直接套用 DMD 公式黑匣子算相当于用不等间距的数据拟合了一个“等间距假设”的线性映射相位全部被扭曲。解决先用插值把数据重采样成等间隔序列。一般用 scipy 的 interp1d 做线性或三次样条插值然后按固定步长取样。插值本身会引入一点平滑但比相位失真可控。重采样之后的 dt 按新的采样间隔填不要沿用原数据的时间戳。5.5 zip 包里的数据与代码版本错配现象代码报错说维度对不上或者数据读出来是乱码处理完伪加密之后能解压了但.mat文件用 scipy 读出来是空的。原因典型的版本错配。.mat有 v7.3 格式基于 HDF5旧版 scipy.io.loadmat 只能读 v7 及以下还有 Python 2 写的 pandas 代码在 Python 3 下读取 pickle 失败。解决先用scipy.io.whosmat检查.mat文件内部变量名和形状确认和代码里预期的一致如果版本过低用 h5py 读 v7.3 文件如果代码里出现xrange或print语句直接改用 Python 3 重写那段逻辑。这个坑的代价不大但很浪费时间先花两分钟检查读入数据的维度再往下跑永远是第一原则。6. 验证与进阶用 DMD 做预测前先回答三个问题把 DMD 集成到自己的分析流程之前我总会要求团队回答三个问题。第一重构误差多大算合格对流场数据我通常看“模态叠加重构”与原始数据在时域上的相对 L2 误差低于 5% 算合格如果到了 10% 以上说明要么 r 太小要么系统非线性太强DMD 的线性近似失效。第二预测步数是多少DMD 是线性外推适合短期预测一般不建议超过训练快照数的 20%超过这个范围误差会呈指数增长发散的早发散不发散的也开始偏离相位。第三模态的物理意义是否可解释如果某个高能模态的频率和你做 CFD 监测到的 Strouhal 数对不上优先检查 dt 和 r别急着把结果写进报告。进阶验证有个顺手好用的方法把数据和 DMD 的预测结果同时做 FFT对比主频。如果两者主频一致说明 DMD 至少在频率捕捉上是可靠的如果不一致那就回到参数检查。这个方法五分钟搞定不需要什么额外工具却能拦住一大批“重构看着挺像、预测一塌糊涂”的翻车现场。我个人的习惯是把 DMD 当作“动力学特征的提取器”而不是“万能预测器”。它最强的场景是给出一套紧凑的模态描述让你知道系统在哪个频率、以什么空间形态在演化至于预测我用它做几步外推来预警趋势真正的定量预测还是交给更专业的模型。保存这份 zip 里的代码时我会在 header 里写下数据来源、dt、r 的取值和验证指标三个月后回看还能知道当初为什么这么设参数。希望这几条经验和参数边界能帮你在自己的数据上少走几步弯路。本文还有配套的精品资源点击获取