时间序列自监督学习实战:VIbCReg框架迁移与性能优化
1. 项目概述当计算机视觉的自监督学习遇上时间序列在机器学习领域获取高质量、大规模的标注数据一直是个老大难问题尤其是在时间序列分析这个方向。无论是工业设备的振动监测、医疗心电信号分析还是金融市场的波动预测标注成本都高得吓人。这时候自监督学习Self-Supervised Learning, SSL就成了我们的“救星”——它能让模型从数据本身的结构中学习无需人工打标签。过去几年计算机视觉CV领域的自监督学习可谓风生水起SimCLR、BYOL、VICReg这些名字大家耳熟能详。它们核心思路很巧妙对同一张图片做两次不同的随机变换比如裁剪、变色然后让模型学会识别出这两个“变体”其实来自同一个源头。学到的这个“识别能力”本质上就是模型捕捉到的、关于图像本质的“表示”Representation这个表示拿来干下游任务比如分类效果出奇的好。一个很自然的问题就来了CV领域这套玩得风生水起的“武功秘籍”能不能照搬到时间序列数据上呢时间序列就是一维的“信号”图像是二维的“画面”形态不同但内在的“学习表示”的逻辑是否相通这正是我最近深入研究并实践的一个课题。我尝试将几种主流的CV自监督框架直接应用于时间序列并重点评估了一个名为VIbCReg的框架。结果令人振奋不仅大部分方法都有效VIbCReg这个“后起之秀”在多个经典时间序列数据集如UCR Archive上的表现甚至超越了部分为时间序列量身定制的专用方法。简单来说VIbCReg可以理解为VICReg框架的一个“增强版”。它通过引入归一化协方差矩阵和迭代归一化层更彻底地实现了特征解相关让学到的表示里每个维度都携带独特信息避免冗余。更妙的是它结构简单使用标准的ResNet编码器、属于非对比式方法免去了复杂的负样本采样并且对图像和时间序列“通吃”。如果你正在处理传感器数据、信号数据苦于没有标签或者想为你的时间序列模型找一个强大的预训练起点那么这篇文章里关于VIbCReg的实战细节、调参心得和避坑指南或许正是你需要的。2. 核心思路解析为什么CV的自监督方法能用于时间序列在深入代码和实验之前我们得先想明白一个根本问题凭什么认为为图像设计的方法能用在看起来完全不同的时间序列上这背后其实有深刻的共通逻辑。2.1 数据增强的桥梁作用无论是图像还是时间序列自监督学习的核心前提是对数据的“合理”变换不应该改变其语义或类别信息。一张猫的图片无论怎么裁剪、旋转、调色它还是猫。同理一段正常心电信号的片段无论其振幅整体缩放模拟不同设备增益或在时间轴上小幅平移模拟心跳起始点差异它依然代表正常心律。在CV中我们使用裁剪、颜色抖动、高斯模糊等作为数据增强。映射到时间序列我们可以找到功能对应的操作随机裁剪 (Random Crop) 对应图像的随机裁剪。从长时间序列中随机截取一段连续子序列。这是最关键、最有效的增强它迫使模型关注局部形态模式而非绝对位置。随机振幅缩放 (Random Amplitude Resize) 对应图像的色彩/亮度调整。将整个时间序列的数值进行全局缩放模拟信号采集时的增益变化。随机垂直平移 (Random Vertical Shift) 对应图像的平移。为整个序列加上一个随机偏置模拟传感器的基础电压漂移。扩展加噪、时间扭曲 类似图像的高斯模糊或弹性变换。加入轻微噪声或对时间轴进行非均匀的拉伸/压缩以增加鲁棒性。通过这些“意义相通”的增强我们为模型构建了同样的自监督预训练任务让模型学会无视这些不影响本质的变换从两个增强视图中提取出不变的、有意义的表示。2.2 编码器的适配与选择图像编码器如ResNet、Vision Transformer处理的是二维网格数据。时间序列是一维数据。直接应用的关键在于调整输入维度。最直接的方式是将1D时间序列视为高度为1的2D图像。这样我们可以几乎原封不动地使用2D卷积核只是将其高度固定为1宽度对应于时间序列的卷积核大小。例如一个3x3的CV卷积核在时间序列上就变成了1x3的一维卷积核。ResNet中的基础模块如BasicBlock或Bottleneck可以很容易地改造成一维版本1D-ResNet仅将所有的2D卷积Conv2d、池化AvgPool2d等操作替换为其一维对应物Conv1d,AvgPool1d。在本次评估中为了公平比较并强调SSL框架本身的作用VIbCReg和大多数对比方法都使用了结构相同的轻量级1D-ResNet作为编码器。这避免了因编码器架构差异巨大而带来的性能混淆让我们能聚焦于SSL损失函数设计带来的影响。注意编码器感受野的局限性这里有一个重要的trade-off。使用标准的ResNet无论是1D还是2D其感受野是固定的、有限的。对于非常长的时间序列它可能只能捕捉局部相关性而忽略长程依赖。这是VIbCReg论文中明确指出的一个局限。对于需要全局上下文的任务如某些长序列分类可能需要引入Transformer、TCN时序卷积网络或更深的、具有更大感受野的架构。但在UCR/UEA等多数数据集中序列长度相对适中1D-ResNet已被证明是有效的。2.3 从对比式到非对比式的演进早期的自监督学习如SimCLR大多是对比式的。它们需要构造正样本对同一源数据的不同增强视图和负样本对不同源数据并拉近正对、推远负对。这带来了负样本采样的复杂性需要足够大的批处理大小Batch Size来提供大量隐式负样本否则性能会急剧下降。同时负样本的质量是否真的是“难负例”也会影响效果。而非对比式方法如BYOL、SimSiam、Barlow Twins、VICReg以及我们的VIbCReg则试图摆脱对负样本的依赖。它们通常只处理正样本对通过其他约束来避免模型坍缩即所有输出都收敛到同一个无意义的点。例如BYOL/SimSiam 使用非对称网络结构、预测头、停止梯度等技巧。Barlow Twins 最小化正对特征向量的跨维度相关性迫使每个特征维度学习独特信息。VICReg/VIbCReg 通过一个三部分的损失函数来约束特征不变性正对特征应相似、方差一个批次内每个特征维度应保持方差高于阈值防止坍缩、协方差不同特征维度间的相关性应最小化防止冗余。VIbCReg的改进正在于此。它在VICReg的协方差损失计算前对特征进行了L2归一化。这一步至关重要。想象一下未经归一化的特征其数值尺度可能差异很大直接计算协方差矩阵会受尺度影响。归一化后我们关注的是特征向量方向之间的相关性而非幅值这使得特征解相关的优化目标更加纯粹和稳定。此外它使用了迭代归一化层IterNorm作为投影头的最后一层这是一种比普通批归一化更强大的白化操作能加速和稳定特征解相关的过程。3. 实战复现VIbCReg在时间序列上的训练与评估理论说得再多不如一行代码。接下来我将带你一步步搭建VIbCReg的训练流程并分享在时间序列数据集以UCR Archive为例上进行线性评估Linear Evaluation的完整过程。这是衡量自监督学习质量的金标准冻结预训练好的编码器只在学到的表示之上训练一个简单的线性分类器如SVM或线性层看其分类精度。3.1 环境搭建与数据准备首先确保你的环境包含PyTorch、Torchvision、Scikit-learn等基础库。我们将使用PyTorch Lightning来组织训练代码这样更清晰。# 基础环境 pip install torch torchvision pytorch-lightning scikit-learn # 用于下载和管理UCR数据集 pip install sktimeUCR数据集是时间序列分类的基准。我们可以用sktime库方便地获取。这里以ElectricDevices数据集为例。import numpy as np import torch from torch.utils.data import Dataset, DataLoader from sktime.datasets import load_UCR_UEA_dataset class UCRDataset(Dataset): 封装UCR数据集为PyTorch Dataset def __init__(self, dataset_name, splittrain): self.X, self.y load_UCR_UEA_dataset(namedataset_name, splitsplit) # sktime返回的数据可能是pd.Series的列表转换为numpy数组 self.X np.array([x.values.flatten() for x in self.X]) # 假设单变量序列 self.y np.array(self.y) # 标准化按每个序列单独进行或使用训练集统计量 self.X (self.X - self.X.mean(axis1, keepdimsTrue)) / (self.X.std(axis1, keepdimsTrue) 1e-8) def __len__(self): return len(self.X) def __getitem__(self, idx): # 返回形状为 (1, seq_len) 的张量模拟单通道图像 x torch.FloatTensor(self.X[idx]).unsqueeze(0) y torch.LongTensor([self.y[idx]]).squeeze() return x, y # 示例加载训练集和测试集 train_dataset UCRDataset(ElectricDevices, splittrain) test_dataset UCRDataset(ElectricDevices, splittest)3.2 数据增强策略的实现根据论文附录A.3我们实现Part-2评估中使用的增强组合随机裁剪多尺度和随机振幅缩放。import torch.nn.functional as F import random class TimeSeriesAugmentation: 时间序列数据增强组合 def __init__(self, base_length, crop_ratios[0.5, 1.0], amplitude_scale0.1): self.base_length base_length self.crop_ratios crop_ratios # 多尺度裁剪比例 self.amplitude_scale amplitude_scale def random_crop(self, x, crop_ratio): 随机裁剪crop_ratio为裁剪长度与原长度之比 crop_len int(self.base_length * crop_ratio) if crop_len self.base_length: return x start random.randint(0, self.base_length - crop_len) # 使用插值将裁剪后的序列缩回原始长度保持时间维度一致 cropped x[:, start:startcrop_len] return F.interpolate(cropped.unsqueeze(0), sizeself.base_length, modelinear, align_cornersFalse).squeeze(0) def random_amplitude_resize(self, x): 随机振幅缩放乘以一个高斯分布的随机因子 scale torch.randn(1, devicex.device) * self.amplitude_scale 1.0 return x * scale def __call__(self, x): 对同一输入生成两个增强视图 # 为每个视图随机选择一个裁剪比例 crop_ratio_a random.choice(self.crop_ratios) crop_ratio_b random.choice(self.crop_ratios) view1 self.random_crop(x, crop_ratio_a) view2 self.random_crop(x, crop_ratio_b) view1 self.random_amplitude_resize(view1) view2 self.random_amplitude_resize(view2) return view1, view2 # 使用示例 aug TimeSeriesAugmentation(base_length96) # ElectricDevices序列长度为96 sample, _ train_dataset[0] view1, view2 aug(sample.unsqueeze(0)) # 输入需要是(batch, channel, length)3.3 VIbCReg模型与损失函数实现这是核心部分。我们按照论文附录A.1和A.2的伪代码进行实现。import torch import torch.nn as nn import torch.nn.functional as F import pytorch_lightning as pl from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR class IterativeNormalization(nn.Module): 迭代归一化层 (IterNorm) 的简化实现。 论文中使用了更复杂的迭代白化此处用一个可学习的线性变换模拟其效果。 在实际复现中建议参考原论文代码实现完整的IterNorm。 def __init__(self, num_features, eps1e-5, num_iter5): super().__init__() self.num_features num_features self.eps eps self.num_iter num_iter # 一个简单的可学习缩放和偏置模拟白化后的调整 self.weight nn.Parameter(torch.ones(num_features)) self.bias nn.Parameter(torch.zeros(num_features)) def forward(self, x): # x shape: (batch_size, num_features) # 简易版先进行层归一化再应用可学习参数 mean x.mean(dim1, keepdimTrue) var x.var(dim1, keepdimTrue, unbiasedFalse) x (x - mean) / torch.sqrt(var self.eps) return self.weight * x self.bias class VIbCRegProjector(nn.Module): VIbCReg的投影头三层MLP最后一层为IterNorm def __init__(self, input_dim512, hidden_dim4096, output_dim4096): super().__init__() self.net nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(inplaceTrue), nn.Linear(hidden_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(inplaceTrue), nn.Linear(hidden_dim, output_dim), IterativeNormalization(output_dim) # 关键使用IterNorm ) def forward(self, x): return self.net(x) class VIbCReg(pl.LightningModule): VIbCReg主模型 def __init__(self, encoder, feature_dim512, projector_dim4096, lambda_25, mu25, nu100): super().__init__() self.encoder encoder # 例如一个1D-ResNet输出维度为feature_dim self.projector VIbCRegProjector(input_dimfeature_dim, hidden_dimprojector_dim, output_dimprojector_dim) self.lambda_ lambda_ self.mu mu self.nu nu self.augmentation TimeSeriesAugmentation(base_length96) # 需根据数据集设置 def forward(self, x): # 用于下游任务的特征提取返回编码器输出不经过投影头 return self.encoder(x) def invariance_loss(self, z_a, z_b): 不变性损失均方误差 return F.mse_loss(z_a, z_b) def variance_loss(self, z): 方差损失鼓励每个特征维度的标准差接近1 std torch.sqrt(z.var(dim0) 1e-4) # 按特征维度计算标准差 return torch.mean(F.relu(1.0 - std)) # hinge loss def covariance_loss(self, z): 协方差损失VIbCReg关键改进计算归一化后特征的协方差矩阵非对角元素平方和 # 1. 中心化 z z - z.mean(dim0) # 2. L2归一化按特征维度 z F.normalize(z, p2, dim0) # 关键步骤 # 3. 计算协方差矩阵 cov torch.mm(z.T, z) / (z.size(0) - 1) # (F, F) # 4. 计算非对角元素的平方和 off_diag cov.flatten()[:-1].view(cov.size(0)-1, cov.size(0)1)[:,1:].flatten() return torch.sum(torch.pow(off_diag, 2)) / z.size(1) # 除以特征维度数 def training_step(self, batch, batch_idx): x, _ batch # 自监督学习不需要标签 total_loss 0 # 多尺度裁剪对每个裁剪比例计算损失 for crop_ratio in [0.5, 1.0]: # 生成两个增强视图此处简化实际应调用augmentation # 为演示我们假设augmentation已集成多尺度逻辑 x_a, x_b self.augmentation(x) # 编码与投影 h_a self.encoder(x_a) h_b self.encoder(x_b) z_a self.projector(h_a) z_b self.projector(h_b) # 计算损失分量 invar_loss self.invariance_loss(z_a, z_b) var_loss (self.variance_loss(z_a) self.variance_loss(z_b)) / 2 cov_loss (self.covariance_loss(z_a) self.covariance_loss(z_b)) / 2 loss self.lambda_ * invar_loss self.mu * var_loss self.nu * cov_loss total_loss loss self.log(train_loss, total_loss, prog_barTrue) return total_loss def configure_optimizers(self): optimizer AdamW(self.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max100) # 假设训练100个epoch return [optimizer], [scheduler]3.4 线性评估协议预训练完成后我们需要评估学到的表示质量。标准做法是冻结编码器在其输出的特征上训练一个简单的线性分类器。from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler import numpy as np def extract_features(model, dataloader, devicecuda): 使用训练好的编码器提取所有数据的特征 model.eval() features, labels [], [] with torch.no_grad(): for x, y in dataloader: x x.to(device) # 注意这里使用encoder而不是整个VIbCReg不经过projector h model.encoder(x) features.append(h.cpu().numpy()) labels.append(y.numpy()) return np.vstack(features), np.concatenate(labels) # 假设我们已经有一个预训练好的VIbCReg模型 pretrained_model pretrained_model.eval() pretrained_model.to(cuda) # 为训练集和测试集提取特征 train_features, train_labels extract_features(pretrained_model, train_loader) test_features, test_labels extract_features(pretrained_model, test_loader) # 标准化特征对SVM很重要 scaler StandardScaler() train_features_scaled scaler.fit_transform(train_features) test_features_scaled scaler.transform(test_features) # 训练一个线性SVM分类器 svm_classifier SVC(kernellinear, C1.0, random_state42) svm_classifier.fit(train_features_scaled, train_labels) # 在测试集上评估 test_accuracy svm_classifier.score(test_features_scaled, test_labels) print(fLinear Evaluation (SVM) Accuracy: {test_accuracy:.4f})4. 实验结果深度分析与调参心得跑通流程只是第一步理解结果背后的原因并知道如何调优才是关键。根据论文中的大量实验结果UCR/UEA数据集上的SVM准确率表格我们可以总结出一些普适性的规律和实操要点。4.1 性能排名与框架选择从论文的Table 5和6UCR数据集的“Mean Rank”行可以清晰看到在众多CV SSL方法中VIbCReg平均排名第2.5表现最佳。SimSiam和SimCLR紧随其后平均排名分别为3.2和3.6。VICRegVIbCReg的前身排名4.7Barlow Twins排名5.5BYOL排名5.7。这个排名告诉我们非对比式方法整体占优排名靠前的VIbCReg、SimSiam都是非对比式。这暗示在时间序列数据上避免负样本采样的复杂性可能是一个优势。特征解相关至关重要VIbCReg改进的VICReg显著优于VICReg而Barlow Twins核心思想也是减少冗余排名却靠后。这说明如何实现特征解相关是关键。VIbCReg的归一化协方差和IterNorm层可能提供了更稳定、更有效的优化路径。SimCLR的稳健性作为经典的对比式方法SimCLR依然表现不俗说明如果计算资源充足能支持大batch size以获得足够负样本它仍然是可靠的选择。实操心得项目启动时的框架选择如果你刚开始一个时间序列自监督项目我的建议是优先尝试VIbCReg或SimSiam。原因如下代码复杂度低两者都不需要负样本采样SimSiam甚至不需要动量编码器实现起来更简单。超参数相对友好VIbCReg的主要超参数lambda_,mu,nu在论文中给出了参考值25, 25, 100/200SimSiam的超参数更少。内存需求小无需为存储负样本或维护动量队列消耗额外内存对硬件更友好。 只有在拥有海量数据和大规模GPU集群时才值得去精细调优SimCLR这类对比方法以追求那可能存在的额外性能提升。4.2 多尺度裁剪的威力与陷阱论文中一个有趣的发现是“Part-1”和“Part-2”实验的差异。Part-1使用固定的、针对每个数据集调优的裁剪长度而Part-2使用了多尺度裁剪同时使用50%和100%长度。结果发现SimSiam与多尺度裁剪有“正向协同效应”而BYOL则出现了“负面效应”。这背后的原因可能与不同方法的稳定性有关。SimSiam结构简单多尺度视图为其提供了更丰富的学习信号。而BYOL依赖动量编码器和对称结构多尺度带来的视图差异过大可能会破坏其基于预测的稳定优化过程。调参避坑指南数据增强策略默认从多尺度裁剪开始对于大多数方法尤其是VIbCReg、SimSiam同时使用[0.5, 1.0]的裁剪比例是一个强力的默认设置。它能迫使模型同时关注局部细节和全局轮廓。警惕“增强过强”对于像BYOL这样结构复杂、对视图一致性要求极高的方法过于激进的数据增强如差异极大的多尺度裁剪可能导致训练不稳定。可以从单一尺度如0.8开始逐步增加增强强度。振幅缩放因数据而异Random Amplitude Resize的缩放因子alpha需要根据数据特性调整。对于标准化后的传感器数据0.1是个不错的起点。但对于像股价收益率这种本身波动就很大的数据可能需要调小甚至移除这个增强以免破坏其相对变化模式。4.3 编码器与投影头设计的细节编码器论文坚持使用轻量级1D-ResNet以保证公平。但在实际应用中如果你的序列非常长如数万点可以考虑使用空洞卷积Dilated Convolutions的TCN来扩大感受野。在ResNet后端加入注意力机制或Transformer层来捕捉长程依赖。直接使用基于Transformer的编码器如Informer、Autoformer的编码部分但要注意其预训练可能与SSL目标存在差异。投影头这是SSL方法性能差异的关键所在。VIbCReg使用了三层MLPIterNorm。经验表明投影头的宽度和深度需要足够大以提供一个“缓冲空间”让表示在投影空间中进行充分的非线性变换和正则化。论文中设置为4096是常见选择。BatchNorm/IterNorm的位置很重要。VIbCReg在投影头的每一层后都使用了BN/ReLU最后是IterNorm。这有助于稳定训练并促进特征解相关。不要在下游任务中使用投影头预训练完成后提取特征用于线性评估或微调时一定只使用编码器encoder的输出。投影头是预训练阶段的专用部件其输出可能过度适应SSL任务本身。5. 常见问题排查与实战技巧在实际复现和调优过程中你肯定会遇到各种问题。下面是我踩过的一些坑和解决方案。5.1 训练损失震荡或不下降症状损失值来回波动没有明确的下降趋势或者早期下降后很快进入平台期。可能原因与排查学习率过大这是最常见原因。SSL任务通常需要比监督学习更小的学习率和更长的预热Warm-up。尝试将初始学习率降低一个数量级例如从1e-3降到1e-4并增加Warm-up epoch。批处理大小Batch Size太小尤其是对于对比式方法SimCLR和依赖批次统计量的方法VICReg/VIbCReg的方差、协方差损失。尽可能使用你GPU能承受的最大Batch Size。对于非对比方法Batch Size的影响相对较小但也不宜过小如小于32。数据增强过强或不合适检查你的增强管道。尝试暂时移除最激进的增强如大幅度的振幅缩放或非常短的裁剪观察损失是否稳定。确保增强不会破坏数据的语义例如对周期性信号进行过长的裁剪可能截断一个完整周期。梯度爆炸/消失检查编码器和投影头中是否有不合适的激活函数或初始化。使用标准的He初始化或Xavier初始化并在关键位置如投影头保留BatchNorm层。5.2 线性评估准确率过低甚至低于随机猜测症状预训练过程看起来正常损失下降但提取特征训练线性分类器后准确率奇低。可能原因与排查特征提取错误最经典的坑确保你在extract_features函数中调用的是model.encoder(x)而不是model(x)或model.projector(model.encoder(x))。后者提取的是投影头输出的、经过SSL任务优化的特征不适合直接用于线性分类。特征维度灾难编码器输出的特征维度可能过高如2048而你的训练样本数很少UCR中有些数据集只有几十个样本。这会导致线性分类器如SVM严重过拟合。解决方案在特征后加入一个PCA降维步骤将维度降至与样本量匹配的规模如50-100维或者使用强正则化的逻辑回归。数据泄露确保在标准化特征时StandardScaler仅使用训练集的统计量均值和方差来变换训练集和测试集。绝对不能在整个数据集上fit scaler。SSL任务本身失败如果上述都排除了那可能预训练根本没学到有用表示。回头检查训练损失曲线确认其确实在优化虽然SSL的损失值本身没有绝对意义但应持续下降。可以可视化一下学到的特征用t-SNE或UMAP看同类样本是否在特征空间中有聚集趋势。5.3 处理超长序列与内存溢出OOM症状训练时GPU内存不足尤其是在使用多尺度裁剪生成长度100%的视图时。解决方案梯度累积如果无法增大Batch Size可以使用梯度累积。例如设置accumulate_grad_batches4在PyTorch Lightning中相当于用4个小批次的平均梯度来更新一次权重模拟了大Batch Size的效果。自动混合精度AMP使用torch.cuda.amp可以显著减少GPU内存占用并加速训练。PyTorch Lightning中只需在Trainer中设置precision16。调整裁剪策略对于超长序列可以放弃使用100%长度的裁剪。改为使用固定大小的滑动窗口进行裁剪或者先对原始序列进行下采样/分段聚合PAA后再输入网络。使用更轻量的编码器如果序列极长可以考虑使用层数更少的ResNet或者将一维卷积的通道数减半。5.4 复现论文结果时的差异症状使用论文提供的超参数但无法达到其报告的准确率。可能原因随机种子深度学习实验对随机种子敏感。确保固定所有随机种子Python, NumPy, PyTorch。数据预处理细节论文中可能使用了特定的标准化方式如按通道标准化、全局标准化。仔细核对附录或代码库。UCR数据集的原始数据可能包含NaN或Inf需要处理。优化器与调度器细节论文可能使用了特定的优化器如LARS for SimCLR with large batch、权重衰减值、学习率调度策略如Cosine decay with warm-up。这些细节对最终性能影响巨大。训练epoch数SSL通常需要更长的训练周期几百甚至上千个epoch才能充分收敛。确保你的训练轮数足够。线性评估协议论文使用的是SVM你用的是逻辑回归SVM的核函数线性、正则化参数C是否经过调优使用相同的评估协议是关键。最后分享一个我个人在时间序列SSL项目中最深刻的体会数据增强的设计比模型架构的微调更重要。花时间理解你的时间序列数据的本质——它的噪声来源是什么哪些变换是“等价的”哪些变换会改变其类别——并据此设计或选择数据增强策略其带来的性能提升往往远大于换一个更复杂的SSL框架。VIbCReg的成功很大程度上也归功于其与适合时间序列的数据增强多尺度裁剪的良好兼容性。从你的数据本身出发让增强策略成为你领域知识的注入点这才是自监督学习在时间序列上发挥威力的关键。