尧图精选

GASF-CNN实战:时间序列转图像分类的全流程指南

🕒 发布时间:2026/9/18 3:23:49 📁 来源:尧图网络
简介面向时序数据分析与深度学习进阶者的完整项目实例文档基于Python实现GASF-CNN分类框架通过格拉姆角场GASF将时序数据转换为图像再由卷积神经网络自动提取特征以解决高维时序数据分类准确率低、人工特征工程依赖强等问题。文档覆盖数据预处理、模型构建与训练、性能评估、GUI设计全流程并对高维时序复杂性、GASF图像质量控制、卷积神经网络过拟合、多领域适配等典型挑战给出了具体解决方案同时拓展到金融预测、医疗数据分析、智能交通和环境监测等应用场景兼顾理论深度与工程落地。整个资源包为1个docx文档约73KB目录结构完整代码详解和设计思路可对照实践方便在现有框架上二次开发。目前已有66人学习下载适合想系统掌握GASF与CNN结合方法并能快速验证的科研人员、数据科学家及工程师。1. 为什么GASF-CNN是时序分类的一个实用组合把一维时序信号直接塞进卷积神经网络效果往往不如预期。原因在于CNN的卷积核天然是为二维空间结构设计的图像里相邻像素有明确的空间邻接关系而一维信号里相隔较远的两个时间步之间卷积核需要堆叠很多层才能建立交互。格拉姆角场GASF的思路很直接把时间序列映射到极坐标系再通过三角函数的和角公式构造一个对称矩阵让每个元素都携带两个时间步之间的角度关系。这样一来原本一维的序列变成了二维图像CNN的卷积核可以直接在时间步对的平面上滑动长程依赖被压缩到了相邻像素里。这套方案对金融波动、设备振动、生理信号这类非平稳时序尤其适用因为它不依赖人工构造的统计特征模型自己从图像纹理里学判别信息。下面按一条完整可复现的链路展开先讲GASF的数学原理和Python实现再做数据预处理与质量控制然后搭建CNN训练评估最后落到参数调节和验证技巧。2. GASF映射原理与Python实现从极坐标到格拉姆矩阵2.1 为什么先归一化到[-1, 1]再取反余弦GASF的第一步是把时间序列X {x₁, x₂, ..., xₙ}缩放至[-1, 1]区间然后计算每个点的反余弦值φᵢ arccos(xᵢ)。这里有两个关键细节需要说清楚。第一归一化的方式不能随意用MinMax的[0, 1]区间。arccos的定义域是[-1, 1]如果把数据压缩到[0, 1]角度取值范围只剩[0, π/2]映射后角度分辨率直接砍半GASF图像的信息熵会显著下降。因此要做的是先MinMax到[0, 1]再乘2减1平移到[-1, 1]。原始值-1对应角度π原始值1对应角度0单调性保持。第二归一化的基准应该在整个训练集的全局统计上计算而不是在每个窗口内部单独归一化。每个样本独立归一化会把不同窗口间的幅值差异抹掉GASF图像只表达形态而不表达能量这会丢失对分类很重要的幅值信息。常见做法是先在完整训练序列上统计min和max然后用同一组统计量转换所有窗口。测试集同样使用训练集的统计量避免数据泄露。2.2 和角公式构造对称矩阵的完整推导与代码GASF的矩阵元素定义是G[i, j] cos(φᵢ φⱼ)。把和角公式展开cos(φᵢ φⱼ) cos(φᵢ)·cos(φⱼ) - sin(φᵢ)·sin(φⱼ)。这里有一个替换技巧cos(φᵢ)就是归一化后的xᵢsin(φᵢ)可以通过√(1 - xᵢ²)算出来。最终形式为G X^T · X - √(I - X²)^T · √(I - X²)其中X是归一化后的行向量√(I - X²)表示对每个元素求√(1 - xᵢ²)。这个形式的好处是把三角函数计算转化为向量内积实现时完全用numpy的矩阵运算完成不需要逐元素循环。代码如下import numpy as np def gasf_transform(series: np.ndarray) - np.ndarray: 将一维时间序列转换为 GASF 图像矩阵 :param series: 1D numpy 数组形状 (win_len,) :return: GASF 矩阵形状 (win_len, win_len) # 1. 归一化到 [-1, 1] x series.astype(np.float32) x (x - x.min()) / (x.max() - x.min() 1e-8) * 2 - 1 # 2. 极坐标角度 cos_phi x sin_phi np.sqrt(np.clip(1 - x ** 2, 0, 1)) # 3. 和角公式构造 Gram 矩阵 gasf cos_phi[:, None] * cos_phi[None, :] - sin_phi[:, None] * sin_phi[None, :] return gasf这段代码用到了numpy的广播机制cos_phi[:, None]把形状从(win_len,)变为(win_len, 1)cos_phi[None, :]变为(1, win_len)两者相乘得到一个(win_len, win_len)的外积矩阵。sin_phi部分同理。np.clip防止浮点误差导致根号内出现负数。加1e-8是为了避免序列恒定不变时max和min相等导致除零。2.3 GASF与GADF的分工加和差分别保留什么信息GASF矩阵里对角线元素是cos(2φᵢ)非对角线是cos(φᵢ φⱼ)。当两个相邻时刻的相位差较小时GASF的值接近1图像表现为亮色区域相位差大时值接近-1表现为暗色。也就是说GASF对两个时刻是否有协同变化非常敏感。还有一张常见的姊妹图叫GADF定义是cos(φᵢ - φⱼ)展开后为X^T·X √(I - X²)^T·√(I - X²)。两者差别在于中间符号GADF保留的是相位差对局部波形变化更敏感GASF保留的是相位和对全局趋势更敏感。实际使用中GASF更适合周期性强、趋势明显的信号GADF更适合突变密集的信号。很多工程实现会把两张图叠加成双通道输入或者与马尔可夫转移场MTF一起堆成三通道效果往往优于单用一张。3. 预处理链路窗口划分、归一化与图像质量控制3.1 窗口长度与步长的选择逻辑GASF转换的首要参数是窗口长度win_len它直接决定输出图像的尺寸。窗口太短比如小于32图像只覆盖不到一个完整周期CNN能学到的纹理极其有限窗口太长比如超过512计算复杂度按O(n²)增长且非平稳信号在长窗口内大概率混入多种状态模式反而增加分类难度。实际项目中一般根据信号的周期或采样率确定。振动信号常见做法是取一个旋转周期的1.5到2倍长度生理信号如心电通常取2到3个心动周期的时长。如果完全没有先验知识就在[64, 128, 256]三个档位做交叉验证对比。步长的选择是在样本量和冗余度之间权衡步长等于窗口长度时样本完全无重叠信息量最少但训练最快步长取窗口的1/4样本量放大4倍相邻窗口高度相关需要用更强的Dropout防止过拟合。这里默认使用步长窗口长度的1/2兼顾效率和样本数量。3.2 归一化的顺序与数据泄露问题预处理顺序不能乱。之前提过先在全量训练数据上计算全局min和max再切窗、再转换GASF。如果顺序反了先切窗再分别归一化每个窗口的幅值尺度就被拉平了跨窗口的幅值差异信息丢失。另一个隐蔽问题是如果在切窗之前做了整段归一化那么测试集的归一化也必须使用训练集的统计量否则min和max的取值范围会漂移。关于缺失值和异常值GASF对这两类问题非常敏感。极坐标映射本质上是一个角度变换异常的幅值尖峰在归一化后会把整个窗口的范围撑大其他正常样本的角度分辨率被压缩导致图像整体偏暗、纹理对比度下降。因此转换前要先用线性插值填补缺失值再用滑动中位数或Savitzky-Golay滤波去除高频毛刺。这一步不能省。3.3 批量切窗与GASF转换的完整代码下面是一段完整的预处理代码把原始长序列切窗、映射到GASF、并划分训练测试集import numpy as np from sklearn.model_selection import train_test_split def sliding_windows(data: np.ndarray, win_len: int, step: int) - np.ndarray: 对一维长序列做滑动窗口切分 :param data: 原始序列 (n_samples,) :param win_len: 窗口长度 :param step: 滑动步长 :return: 窗口数组 (n_windows, win_len) windows [] for start in range(0, len(data) - win_len 1, step): windows.append(data[start:start win_len]) return np.array(windows) # 以三分类合成振动信号为例低速/中速/高速 fs 1000 # 采样率 1000 Hz t np.arange(0, 20, 1 / fs) # 20 秒信号 freqs [5, 12, 25] # 三类工况对应的主频 raw_signals [] labels [] for cls, f in enumerate(freqs): signal (0.5 * np.sin(2 * np.pi * f * t) 0.2 * np.sin(2 * np.pi * f * 2 * t) 0.1 * np.random.randn(len(t))) raw_signals.append(signal) labels.extend([cls] * (len(t) // 256)) # 粗切标签 raw_data np.concatenate(raw_signals) global_min raw_data.min() global_max raw_data.max() raw_norm (raw_data - global_min) / (global_max - global_min 1e-8) * 2 - 1 win_len, step 128, 64 windows sliding_windows(raw_norm, win_len, step) gasf_images np.array([gasf_transform(w) for w in windows]) gasf_images gasf_images[..., np.newaxis] # 加通道维度 (n, 128, 128, 1) X_train, X_test, y_train, y_test train_test_split( gasf_images, labels[:len(gasf_images)], test_size0.3, stratifylabels[:len(gasf_images)], random_state42 ) print(f训练集: {X_train.shape}, 测试集: {X_test.shape})代码里把归一化放在切窗之前并且不是每个窗口独立计算。stratify参数保证训练集和测试集的类别比例一致对不平衡数据尤为重要。需要说明的是这里的标签是按照时间段粗切的实际工程中标签应该来自传感器所对应时刻的真实工况记录而不是按时间均匀分配。3.4 工况数据量不足时的数据扩展手段当原始序列短、窗口数量不够时可以叠加GASF和GADF构成双通道输入这相当于在不改变采样序列的前提下扩展了特征视图。另一种常用做法是窗口重叠更大比如步长取窗口的1/8让相邻窗口高度重叠。但这时一定要配合强正则化否则相邻窗口的强相关性会让模型在训练集上表现极好、验证集上崩溃。如果使用的是小数据集这两条路优先于直接加深网络。4. CNN分类网络搭建、训练流程与多指标评估4.1 三层卷积加两层全连接的网络结构GASF图像的尺寸是win_len × win_len对128的窗口尺寸来说网络不需要太深。卷积核的视野覆盖的是一对时间步及其邻居之间的关系第一层就用小卷积核。下面是网络结构的完整配置层名输出尺寸核大小 / 参数说明Input128×128×1-单通道GASF图Conv2D126×126×323×3, 32核, ReLU捕捉相邻时间步对的局部纹理MaxPool63×63×322×2缩小尺寸增大感受野Conv2D61×61×643×3, 64核, ReLU中层特征MaxPool30×30×642×2-Conv2D28×28×1283×3, 128核, ReLU深层抽象特征GlobalAvgPool128-替代Flatten减少参数量Dense64ReLU, Dropout 0.5分类头前一层Dense3Softmax三分类输出为什么这里用GlobalAveragePooling2D而不是Flatten因为Flatten之后参数量是28×28×128 100352再接全连接层会产生上百万参数而GASF图像本来信息密度高冗余大全连接层极容易过拟合。全局平均池化把每个特征图压成一个标量参数量直接降两个数量级准确率损失通常不到1%换来的是训练稳定性和泛化能力。4.2 训练配置、早停与过拟合抑制模型用TensorFlow/Keras实现训练过程要同时搭配早停、学习率衰减和模型检查点。训练阶段还要输出中间层的可视化结果来确认网络学到了什么而不是盲目调参。下面是可以直接运行的训练代码import tensorflow as tf from tensorflow.keras import layers, models, callbacks def build_gasf_cnn(input_shape(128, 128, 1), num_classes3): model models.Sequential([ layers.Input(shapeinput_shape), layers.Conv2D(32, 3, activationrelu, paddingvalid), layers.MaxPooling2D(2), layers.Conv2D(64, 3, activationrelu, paddingvalid), layers.MaxPooling2D(2), layers.Conv2D(128, 3, activationrelu, paddingvalid), layers.GlobalAveragePooling2D(), layers.Dense(64, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losssparse_categorical_crossentropy, metrics[accuracy] ) return model model build_gasf_cnn() model.summary() # 训练回调早停 动态学习率 保存最优权值 checkpoint callbacks.ModelCheckpoint( best_gasf_cnn.keras, monitorval_accuracy, save_best_onlyTrue, modemax, verbose1 ) early_stop callbacks.EarlyStopping( monitorval_loss, patience15, restore_best_weightsTrue ) reduce_lr callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-6, verbose1 ) history model.fit( X_train, y_train, validation_split0.2, epochs100, batch_size32, callbacks[checkpoint, early_stop, reduce_lr], verbose1 )这里learning_rate设为1e-3配合ReduceLROnPlateau在验证损失停滞时自动减半可以省去手动调学习率的麻烦。patience15表示15个epoch内验证损失没有改善就停止训练restore_best_weights确保返回的是验证集上最好的那组权重而不是最后一轮。4.3 多指标评估混淆矩阵、ROC曲线与误差热图只用准确率评估时序分类模型是不充分的尤其是类别不平衡时。测试集上的完整评估必须包含混淆矩阵、每类别的精确率/召回率/F1以及ROC曲线。如果做的是回归或置信度输出还要检查残差分布。下面是评估代码import matplotlib.pyplot as plt from sklearn.metrics import (confusion_matrix, ConfusionMatrixDisplay, classification_report, roc_curve, auc) from sklearn.preprocessing import label_binarize # 预测 y_proba model.predict(X_test) y_pred y_proba.argmax(axis1) # 每个类别的精确率/召回率/F1 print(classification_report(y_test, y_pred)) # 混淆矩阵 cm confusion_matrix(y_test, y_pred) ConfusionMatrixDisplay(cm, display_labels[低速, 中速, 高速]).plot(cmapBlues) plt.title(Confusion Matrix) plt.show() # ROC 曲线多分类 one-vs-rest y_bin label_binarize(y_test, classes[0, 1, 2]) for i in range(3): fpr, tpr, _ roc_curve(y_bin[:, i], y_proba[:, i]) roc_auc auc(fpr, tpr) plt.plot(fpr, tpr, labelfClass {i} (AUC {roc_auc:.3f})) plt.plot([0, 1], [0, 1], k--) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.legend() plt.title(ROC Curves) plt.show()classification_report输出每一类的精确率、召回率、F1和宏平均/加权平均能直观看出哪一类容易被混淆。混淆矩阵配合ROC曲线可以判断错误是集中在相邻类别还是随机散布。需要特别注意的是如果测试集是用相同工况时间段切出的窗口相邻窗口高度相关评估结果会偏乐观。更严谨的做法是按时间段切分前80%时间段的窗口做训练后20%时间段做测试模拟真实在线应用。5. GASF参数调节、图像可视化验证与多通道扩展5.1 窗口长度与步长的联动调整窗口长度调整后CNN输入尺寸改变网络结构里的Input层和全连接层都要同步调整。一个省事的做法是保持网络结构不变只用GlobalAveragePooling把特征图尺寸自适应压平这样窗口长度改变时只需要改input_shape。从模型性能角度看窗口太短小于一个周期时GASF图像表现为几条平行的斜向纹理类别区分度低窗口合适时图像出现明显的周期性明暗条纹窗口过长时图像纹理被多个周期的叠加打乱同样难以区分。步长与窗口长度的比例影响样本量和样本独立性。我一般不会让步长小于窗口长度的1/8否则相邻样本的GASF图像相似度超过90%训练集和验证集的信息高度重叠验证指标会虚高。如果样本量确实不足优先考虑数据扩展而不是加大重叠。5.2 用GASF图像可视化直接验证转换质量一个容易忽略但极其有效的调试手段是把训练集中各类别的GASF图像打印出来看。如果类别之间的图像纹理没有肉眼可辨的差异不必先调CNN问题大概率出在预处理环节。可视化代码import matplotlib.pyplot as plt fig, axes plt.subplots(2, 3, figsize(9, 6)) for row in range(2): for col in range(3): idx np.where(y_test col)[0][row * 10] axes[row, col].imshow(X_test[idx].squeeze(), cmapviridis, interpolationnearest) axes[row, col].set_title(fClass {col} - sample {row * 10}) plt.tight_layout() plt.show()正常可视化结果应该是同类样本的GASF图像呈现相似的条纹/网状纹理不同类有明显差异。如果图像整体均匀无结构检查归一化是否被全局min-max压得过扁如果图像出现单像素异常亮点说明原始信号有未处理的尖峰噪声。这一步排错比直接看CNN的loss曲线要快得多。也可以用Grad-CAM观察CNN在GASF图像上的激活区域验证模型是否聚焦在GASF图像中有物理意义的时间步对区域。5.3 把GASF与GADF、MTF叠加成多通道输入GASF-CNN的上界不止单张图。GADF和MTF分别是另外两种时序转图像的方案GADF用相位差MTF用马尔可夫转移概率三张图分别从趋势、相位跳变、状态转移三个视角描述同一段序列。把三者堆叠成3通道输入CNN的第一层卷积核会同时跨通道提取不同视角的特征组合。实现时只需把每个窗口分别做三种变换然后在通道维度拼接stack([gasf, gadf, mtf], axis-1)。网络输入通道数从1改为3其余结构不变。这一步带来的准确率提升在公开人体活动识别数据集上通常有2到5个百分点代价是预处理时间约增加到原来的3倍和显存占用增加约1.5倍。对于显存受限的场景也可以用GASFGADF双通道版本效果介于单通道和三通道之间训练速度几乎不受影响。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →