ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

一维序列转二维图像:GAF、MTF、递归图与STFT方法详解

一维序列转二维图像:GAF、MTF、递归图与STFT方法详解 一维序列转二维图像这几年在工业界和学术界都快被聊烂了。很多人第一次听到这个操作会觉得莫名其妙好好的振动信号、股价曲线、脑电波形为什么非要折腾成一张图片真做进去之后才发现图像化不是花架子而是给 CNN 这类模型喂饭的标准姿势。做故障诊断这两年我几乎每天都在和这几种编码方法打交道这篇就把用得最多的四类方法一次讲透GAFS严格说应该叫 GAF也就是格拉姆角场、MTF马尔可夫转移场、Recurrence plot递归图、STFT短时傅里叶变换。不管你是在做轴承故障分类、手势识别、心电分析还是想拿时序数据做 AI 分类这篇文章都能给你一套能直接落地的参考。1. 为什么要做“序列变图像”1.1 一维数据为何要走“图像化”这条路先说一个很现实的问题大多数成熟好用的 CNN 图像分类模型接收的都是二维输入。直接把一维序列塞进 ResNet、EfficientNet结构上就得大改。与其费劲把卷积改成时序版不如反过来把序列变成图像直接享受深度学习视觉领域积累下来的全套工具链。这还不只是工程便利的问题。一维序列的时域波形本质上把时间相关性压缩在一个维度里很多信息是隐蔽的。比如轴承早期故障的冲击特征它在原始波形上是短促的尖峰看着不显眼但经过 GAF 或者 STFT 转换后会在二维平面上形成非常规律的纹理肉眼都能分辨出差异。图像化等于把时序数据里的局部相关结构、周期模式、状态转移关系“摊开”到平面上让特征更容易被注意力机制和卷积核捕捉。另外一个容易被忽略的好处是数据增强。图片可以翻转、裁剪、加噪、缩放而一维序列能做的增强手段很少无非是加噪声、时间扭曲、缩放。一旦转成图像就能用图像领域现成的 augmentation 策略对提升模型泛化能力非常有帮助。所以你看这四种方法本质上做的是同一件事把时间轴上的相关性重新映射到空间轴上让特征分布更适合视觉模型去读。1.2 四类方法分别适合哪些信号虽然目标一致但这四种方法的数学出身完全不同看到的信息也各有侧重。GAF 来自格拉姆矩阵和极坐标映射擅长保留时间点之间的角度关系适合周期性强、趋势明显的信号。MTF 来自马尔可夫链和状态转移概率它把时间序列看成状态的游走适合分析状态切换、趋势转变这类过程。Recurrence plot 来自非线性动力学通过相空间重构描述系统状态的“重现”行为对混沌系统、复杂动态过程很有效。STFT 来自信号处理本质是加窗傅里叶变换直接给出时频域的能量分布适合非平稳信号比如音频、振动、生物电信号。很多人一上来就想分个高下问哪个最好。我的回答通常都是没有最好的方法只有最匹配你数据特征的方法。接下来我把每个方法的数学逻辑、实操步骤、关键参数和踩坑点都过一遍你看完就知道怎么选了。2. GAFS 就是 GAF格拉姆角场编码2.1 核心原理从数值到角度再做内积矩阵GAF 的做法非常巧妙核心分三步。第一步把一维序列归一化。一般会把原始数据缩放到 [0,1] 区间再变换到 [-1,1] 区间。假设原始序列为 x {x_1, x_2, ..., x_n}那么归一化后的值为x_i ((x_i - min(x)) / (max(x) - min(x)) * 2) - 1第二步将归一化后的数值转换成极坐标。对每个值取反余弦得到角度φ_i arccos(x_i)同时把时间戳映射为半径r_i i / N这个技巧是整个方法的精髓。arccos 把 [-1,1] 区间的值映射到 [0,π] 角度空间所以归一化时不能偷懒必须严格映射到 [-1,1]。这样一来数值大小就变成了角度大小时间顺序就变成了半径位置一维序列被完整表达为极坐标系里的一条曲线。第三步用格拉姆矩阵把极坐标上的点展开成二维平面。经典的做法有两种GASFGramian Angular Summation FieldG[i,j] cos(φ_i φ_j)GADFGramian Angular Difference FieldG[i,j] sin(φ_i - φ_j)为什么要用三角函数的和差而不是直接用内积因为在高维空间中cos(φ_i φ_j) 和 sin(φ_i - φ_j) 可以对应到某种“非线性内积”它保留了两个时间点之间的相对角度关系同时避免了一维内积矩阵对角线信息被数值大小主导的问题。说白了GAF 图里每个像素点都代表两个时刻之间的角度相关性像素值越大说明这两个时刻的走势越“同向”。下面是用 numpy 手写的最简版实现import numpy as np def gasf(x): # 归一化到 [-1, 1] x (x - x.min()) / (x.max() - x.min()) x x * 2 - 1 # 极坐标角度 phi np.arccos(x) # GASF 矩阵cos(phi_i phi_j) g np.cos(phi[:, None] phi[None, :]) return g def gadf(x): x (x - x.min()) / (x.max() - x.min()) x x * 2 - 1 phi np.arccos(x) # GADF 矩阵sin(phi_i - phi_j) g np.sin(phi[:, None] - phi[None, :]) return g实际工程里我更推荐直接用 pyts 库接口更稳定少了手写代码出 bug 的机会from pyts.image import GramianAngularField gasf GramianAngularField(methodsummation, image_size28) gadf GramianAngularField(methoddifference, image_size28)注意 image_size 参数它会把生成的矩阵降采样到指定尺寸。为什么要降采样因为在 n 个时间点的情况下GAF 会生成 n×n 的矩阵一维序列稍微长一点内存就吃不消了。2.2 GASF 还是 GADF怎么选很多人第一次看到 GASF 和 GADF 两个选项就懵了不知道用哪个。我直接说结论如果你的信号以趋势变化为主比如股价走势、温度变化GASF 更合适因为它把两个时刻的数值相加角度表达出来保留了较完整的幅值信息如果你的信号以突变、瞬态特征为主比如振动信号的冲击脉冲GADF 更锐利它对差分变化更敏感纹理边界往往更清晰。当然更稳妥的做法是两个都算作为两通道拼在一起输入模型。我在一个轴承故障诊断项目里就是把 GAF 的三通道和原始波形的一个通道拼接成四通道输入效果比单用任何一种都好。图像编码方法不冲突必要时可以组合使用。2.3 工程上的两个大坑第一个坑是内存爆炸。GAF 的复杂度是 O(n²)一段 10 万点的振动信号直接算会产生 100 亿个元素任何机器都扛不住。所以要么先降采样到几百点要么用 image_size 强制控制输出尺寸。我的习惯是先用 1D 平均池化把序列压缩到 100~300 个点再做 GAF信息损失可接受内存压力小很多。第二个坑是归一化标准不统一。训练集和测试集如果分别做 min-max 归一化得到的角度体系就完全不一样模型等于在两张不同“坐标系”的图上训练和预测效果一定会崩。正确做法是通过训练集统计出全局 min 和 max测试集沿用同一组数值做归一化。这个原则在 MTF 和递归图里同样适用。3. MTF马尔可夫转移场3.1 先答热搜问题MTF 是什么不是什么热词是马尔可夫转移场最近搜“mtf是什么”搜到这里的人应该不少。先明确一点在信号处理和深度学习语境下MTF 全称 Markov Transition Field中文叫马尔可夫转移场。它跟网络论坛里那些乱七八糟的缩略词完全不是一回事。你只要在做时间序列分类、状态识别、故障诊断碰到 MTF十有八九就是指这个。马尔可夫转移场的核心思想很朴素把一维时间序列看成一条在若干状态之间游走的路径统计状态之间转移的概率然后把时间信息叠加上去形成一张二维图像。相比 GAF 只刻画数值相关性MTF 更关心状态和状态之间的“跳转规律”。3.2 核心四步分箱、统计转移、映射展开、压缩尺寸MTF 的构造流程通常分四步。第一步把连续数值离散化为 Q 个分箱。一般用分位数分箱因为时间序列的数值分布通常不均衡直接等宽分箱容易出现某些箱子里一个点都没有的情况。分箱后每个时间点 t 得到一个状态值 q_t范围是 0 到 Q-1。第二步统计马尔可夫转移矩阵 W。W[i][j] 表示从状态 i 转移到状态 j 的概率计算方式是统计所有相邻时间点的状态对再按行归一化W[i][j] count(q_t i, q_{t1} j) / count(q_t i)第三步把一维转移矩阵扩展为二维转移场。对任意两个时间点 i 和 jMTF 图上的像素值定义为M[i][j] W[q_i][q_j]也就是说图中每个点代表“i 时刻所处的状态”转移到“j 时刻所处的状态”的概率。这一步是 MTF 与 GAF 最大的不同它把状态游走关系从相邻时刻扩展到了任意两个时刻之间形成全局的状态关联图。第四步控制图像尺寸。原始 M 是 N×N 的矩阵跟 GAF 一样存在 O(n²) 的空间复杂度。工程上通常把时间轴分成若干窗口对窗口内像素做平均池化得到固定大小的图像。pyts 里的 image_size 参数就是这个作用。下面是一个简化版的手写实现import numpy as np def mtf(x, n_bins8): # 分位数分箱 edges np.percentile(x, np.linspace(0, 100, n_bins 1)) edges np.unique(edges) q np.digitize(x, edges[1:-1]) n len(q) # 统计转移矩阵 W np.zeros((n_bins, n_bins)) for i in range(n - 1): W[q[i], q[i 1]] 1 # 行归一化 row_sums W.sum(axis1, keepdimsTrue) row_sums[row_sums 0] 1 # 防止分母为零 W W / row_sums # 展开成转移场 M W[q][:, q] return M实际项目里直接调 pyts 更省事from pyts.image import MarkovTransitionField mtf MarkovTransitionField(image_size28, n_bins8) M mtf.transform(x.reshape(1, -1))[0]3.3 分箱数、图像尺寸参数选择的平衡术MTF 里最关键的参数就是 n_bins。分箱数太小状态空间太粗糙区分度不够分箱数太大每个箱子里的样本太少转移概率估计不稳定。我的经验是从 8 到 16 之间试大多数场景 8 个分箱已经够用了信号类别特别多的时候再往 16 靠。image_size 参数则决定了最终喂给模型的图尺寸。我建议不要一上来就设定 224×224MTF 本质是概率场的可视化分辨率太高并不会增加多少有效信息反而会放大稀疏矩阵里的统计噪声。先用 28×28 或 32×32 这种小尺寸跑通流程再根据模型表现调整。这里再提醒一个实操细节MTF 对数值分布极端敏感。如果原始序列里有明显的离群点分位数分箱会把大量正常数据压到很少的箱子里导致图像纹理扭曲。建议先做去异常点处理比如用中值滤波、剔除 3σ 以外的点再做 MTF。别小看这一步很多项目 MTF 效果差问题就出在预处理。3.4 什么时候优先用 MTFMTF 最擅长表达状态转移过程。举个例子采集一段电机的启动、匀速、停止三段过程原始波形肉眼可能难以快速区分三种工况的边界但 MTF 图上三种工况对应区域的纹理会有非常明显的分块差异因为状态转移模式变了。类似地在识别人的动作意图、机械设备的工况切换、人体姿态转换这类任务里MTF 的纹理可解释性强效果往往比 GAF 更直观。4. Recurrence plot递归图非线性动力学的指纹4.1 原理系统状态的重现递归图这个概念最早来自非线性时间序列分析领域外接触的人不算多但在混沌分析和故障诊断里地位相当高。它的原始定义很简洁如果系统在 t 时刻的状态与 s 时刻的状态足够接近就认为发生了“递归”在图上埋一个点。换句话说递归图把时间序列转换成一张展示“状态多久后回到老地方”的二维图。周期性信号会呈现规律的对角线纹路随机信号则像打散的噪声非线性系统则会出现复杂的分形纹理这些纹理就是系统动力学的指纹。标准的构造过程需要做相空间重构。为了从一维序列恢复系统的多维状态一般用 Takens 嵌入定理构造延迟向量Y_t [x_t, x_{tτ}, x_{t2τ}, ..., x_{t(m-1)τ}]其中 τ 是延迟步长m 是嵌入维数。这样就把一维时间序列提升到 m 维相空间里。然后计算任意两个状态向量的距离D[i][j] ||Y_i - Y_j||再根据阈值 ε 进行二值化R[i][j] 1如果 D[i][j] ≤ ε否则 R[i][j] 0也可以用距离矩阵归一化后直接作为灰度图不一定非要二值化。很多人偷懒直接用原始序列值而不做嵌入这在简单信号上也能用但一旦遇到混沌或强非线性系统纹理信息会严重丢失不建议跳过。手写一个简化版import numpy as np def recurrence_plot(x, tau1, dim1, epsNone): n len(x) - (dim - 1) * tau # 相空间重构 Y np.array([x[i:i dim * tau:tau] for i in range(n)]) # 距离矩阵 D np.sqrt(((Y[:, None, :] - Y[None, :, :]) ** 2).sum(-1)) if eps is None: # 固定为距离分布分位数 eps np.percentile(D, 10) R (D eps).astype(float) return Rpyts 方式from pyts.image import RecurrencePlot rp RecurrencePlot(thresholdpoint, percentage10) R rp.transform(x.reshape(1, -1))[0]4.2 阈值怎么定递归率与纹理的对抗递归图最大的坑在阈值 ε。ε 取得太大几乎所有点都判定为递归图像变成一团黑ε 取得太小图像全是零散的孤立点看不到完整结构。最常用的经验法则是取距离矩阵的某个分位数比如让图像中递归点占比为 5%15%。这个占比在递归分析里叫递归率是判断阈值的核心指标。还有一点要注意如果原始序列的幅度差异很大距离矩阵会被少数大幅值主导递归结构被淹没。这时候最好先做 z-score 标准化再构造递归图。我在处理不同传感器采集的振动信号时经常遇到幅值差 10 倍以上的情况没有标准化之前递归图完全没法看。4.3 纹理模式能告诉我们什么递归图最有意思的地方在于不同纹理对应不同的系统行为这给故障诊断带来了很好的可解释性。对角方向的长短线结构系统具有确定性可能是周期或准周期运动。轴承正常回转信号通常是规则的斜纹。水平或垂直方向的条纹状态在某处保持较长时间常见于缓慢的漂移过程。孤立团块突发性冲击或噪声。大面积均匀纹理随机信号比如白噪声。所以如果你想预测的不是“类别”而是想解释“系统状态发生了什么变化”递归图是四种方法里信息最丰富的一个。代价是计算量大尤其是距离矩阵那一层内存和时间成本比 GAF 还高长序列需要先降采样或者分块计算。5. STFT短时傅里叶变换与时频谱5.1 从傅里叶到窗口原理与分辨率限制STFT 是这四个方法里最“正统”的信号处理方法原理一句话就能说明把一段信号切成一帧一帧的短片段对每一帧做傅里叶变换再把结果按时间顺序拼起来就得到一张以时间为横轴、频率为纵轴、颜色表示能量大小的二维图也就是频谱图。数学表达式是这样的X(t, f) Σ x(τ) w(τ - t) e^{-j2πfτ}其中 w 是窗函数常用的有汉宁窗、汉明窗、布莱克曼窗。窗函数的作用是让每个片段两端平滑过渡到 0避免频谱泄露。STFT 有个绕不开的限制叫海森堡不确定性原理。窗口越短时间分辨率越高但频率分辨率越差窗口越长频率分辨率越好但时间上就越“糊”。一段信号里想要同时精确知道“在哪个时刻”和“在哪个频率”上是做不到的只能做折中。这个道理听起来抽象用图片理解就很好懂窄窗的频谱图竖条清晰、横条模糊宽窗的频谱图横条清晰、竖条模糊。5.2 窗长、重叠比例、FFT 点数的正确姿势我用 scipy 做了大量 STFT 计算最常用的一组参数如下from scipy.signal import stft import numpy as np # 假设采样率 fs 1000 Hz f, t, Zxx stft( x, fs1000, windowhann, nperseg256, noverlap128, nfft512, boundaryzeros ) # 取幅度谱 S np.abs(Zxx) # 转对数幅度谱加 1e-6 防止 log(0) S_log 20 * np.log10(S 1e-6)这里的核心参数有这么几个nperseg窗口长度窗口越长频率分辨率越高。nperseg 256 在 1000 Hz 采样下频率分辨率约 1000/256 ≈ 3.9 Hz如果 nperseg 512频率分辨率约 1.95 Hz。noverlap重叠长度一般取窗口长度的 50% 到 75%。重叠越高时间维度越平滑代价是计算量增大。75% 重叠会让图更细腻是频谱图可视化常用的值。nfftFFT 点数如果希望频率轴更精细可以大于窗口长度相当于对频谱做插值但不会真正提升频率分辨率。window汉宁窗最通用汉明窗更偏“压低旁瓣”布莱克曼窗旁瓣更低但主瓣更宽。我的习惯是先用汉宁窗有问题再试其他。从一个实际问题出发你要区分两个相差 5 Hz 的谱峰那么 nperseg 至少要大于 fs / 5。如果采样率是 1000 Hznperseg 至少 200取 256 正好。想看信号在 0.1 秒内有没有突变窗口就不能超过 0.1 秒对应的采样点数也就是 100 个点。这说明参数是跟着业务需求走的。5.3 从复数谱到喂给 CNN 的图像STFT 的输出是复数矩阵不能直接当图像用。最常用的是取幅度谱再取对数压缩。为什么取对数因为振动信号、音频信号的能量分布在低频段往往很强高频段很弱差好几个数量级直接显示图片会一片黑一片亮对数压缩能把动态范围压回可显示、可学习的范围。具体得到图以后通常有两种用法。第一种是直接保存成单通道灰度图或者用 matplotlib 的 imshow 配合 colormap 保存成彩色图。第二种是提取 t、f、S 矩阵在训练时准实时地转为张量。我倾向第二种因为保存图片会有 JPEG 压缩失真对数谱转成的 PNG 格式也会丢失动态范围直接保留 numpy 数组信息最完整。STFT 图还有一个优势它有严格的物理含义横轴是时间纵轴是频率。所以在做模型解释、故障特征可视化时STFT 是最容易向非技术背景同事解释的方法。你指着图说“这里在 0.3 秒时出现了一个 400 Hz 的频率成分”对方马上能理解。6. 四种方法怎么选一张表搞定6.1 方法对照表我把四种方法的关键属性整理成一张表方便快速对比方法常见缩写输出矩阵核心参数擅长场景主要弱点格拉姆角场GAF/GASF/GADFN×N编码方式image_size周期趋势、相关性结构对噪声敏感O(n²) 内存马尔可夫转移场MTFN×N可压缩n_bins, image_size状态转移、工况切换、变化点分箱敏感概率矩阵有信息损失递归图RPN×Nτ, m, ε非线性系统、混沌、动力学结构参数多计算量大短时傅里叶变换STFTF×T窗长, 重叠, nfft非平稳信号、频带差异、瞬态特征时频分辨率折中6.2 按场景拆解选择逻辑实际项目里怎么选我给你几条判断路径。如果你的任务是传统的信号分类比如轴承故障、电机状态、语音命令先试 STFT。因为它物理意义清晰振动和音频领域的经验积累最多公开论文里也都是以 STFT 为主。跑通之后如果效果不够再加 GAF 作为补充通道。如果你的信号是强周期或准周期类型比如齿轮箱振动、心电信号、季节性的负荷曲线优先 GAF。周期结构在 GAF 上会形成规律性很强的纹理CNN 很容易提取。如果你关注的重点是过程状态的变化比如一个动作从开始到结束、一台设备从正常到退化优先 MTF。它的状态转移概率能够捕捉“变化发生在哪”和“变化有多剧烈”。如果你的目标包含异常检测且数据具备混沌特征比如流体系统、经济系统中的非线性行为递归图优先。它能看到其他方法看不到的状态回归结构。如果计算资源和存储都非常紧张优先 STFT因为 STFT 复杂度是 O(n log n)图像尺寸是 F×T远小于 N×N长序列最友好。补充一点这些选择不是互斥的。我做过一个刀具磨损预测项目最终方案是“STFT 频谱图 GAF 图 原始时域特征”三通道融合。多个视角互补比单个方法硬扛稳定很多。代价是预处理链路变长但对精度要求高的场景值得。7. 实操避坑与一套可复现的最小流程7.1 最容易踩的三个坑第一个坑是图像尺寸失控。GAF、MTF、RP 这三类方法生成的都是 N×N 矩阵序列长度 1000矩阵就是 100 万个点序列长度 10000矩阵就是 1 亿个点。很多人第一次跑长序列训练数据不是内存爆掉就是显存爆掉。解决办法很简单先用平均池化或滑动窗将序列压缩到 100500 个点再编码。如果必须保留长序列信息就切成多个片段分别编码后按通道或按时间拼接。第二个坑是整图归一化。图像化之后很多人顺手用 ImageNet 的 mean/std 做标准化完全忽略了 GAF、MTF、RP 的像素范围并不是自然图像的 RGB 分布。GAF 的取值范围严格在 [-1,1]MTF 是 [0,1] 的概率值RP 是 {0,1} 的二值矩阵STFT 是对数能量各有各的量纲。建议对每个编码方法单独统计训练集上的像素均值和方法再对输入张量做标准化而不是套用现成的视觉模型预处理参数。第三个坑是标签泄漏。在构造 MTF 转移矩阵或者 GAF 归一化时如果混入了测试集数据参与统计模型精度会虚高线下表现和线上缓存对不上。最容易出问题的地方就是 min-max 的 min/max、z-score 的均值方差、MTF 分位数边界这些统计量。这些数值必须只从训练集计算然后以固定值方式应用到验证集和测试集。7.2 预处理红线先滤波、去趋势再做图无论选哪种方法预处理顺序都值得重新检查一遍。我建议的固定流水线是先去均值、去线性趋势再根据业务决定是否滤波最后做异常值剔除和归一化最后做图像化编码。去趋势很重要。比如温度传感器数据有缓慢漂移这段趋势如果不去掉GAF 的角度会整体偏移MTF 的分箱全被趋势主导递归图的距离矩阵也被漂移淹没只有 STFT 相对抗干扰。滤波也一样如果目标特征是高频冲击而信号里混有大量 50Hz 工频干扰不先滤波图像纹理里全是工频条纹。7.3 我习惯的四步落地流程最后分享一套我反复使用的最小落地流程新项目我基本都是照这个路径走。第一步准备数据。确定采样率和类别分布分好训练集、验证集、测试集保证三个集合按时间顺序切割避免随机切分导致数据泄漏。第二步快速可视化。从每个类别里取一条典型样本四种方法各生成一张图肉眼对比纹理差异。如果两类样本在某种编码下差异明显就用这种方法如果四种方法都糊成一片先回头检查预处理不要急着调网络。第三步批量生成并缓存。把编码规则写成函数批量生成图像或张量存成 npy、h5 或 tfrecord 格式。不要在训练循环里边编码边喂数据除非你有很强的 CPU 并行能力否则训练速度会被拖垮几十倍。第四步从小模型开始调参。先用 28×28 或 32×32 的小图配一个浅层 CNN跑通流程。确认有效后再增大图像尺寸、加深网络、叠加多通道输入。这样做能避免一开始就在大图、大模型上反复试错浪费时间。这套流程不是为了炫技而是踩了太多坑总结出来的实用顺序。你照着跑至少能确保每个环节都可以定位问题而不是最后模型效果差却不知道锅该甩给编码、预处理还是网络结构。说点实际的收尾吧。我做故障诊断项目时最常用的组合是“原始波形 STFT GAF”三通道输入。这不是什么玄学而是因为 STFT 对瞬态冲击敏感GAF 对周期趋势敏感两者互补。MTF 和递归图则更像场景专用工具需要画“状态转移”时用 MTF需要画“动力学结构”时用递归图。最后再分享一个实操习惯拿到新数据先别急着批量生成图像先拿一条最有代表性的样本四种方法各画一张图放大看纹理。同一个信号四种编码出来的图案差异非常大但哪个方向更值得投入看一眼图基本就有数了。
返回列表