
简介本资源是一套面向深度学习与工业智能应用研究者的多模态异常检测实践方案聚焦Transformer架构在视觉、声音及时间序列等异构数据融合中的建模能力解决工业监测、系统运维与智能安防等场景下的无监督异常识别问题。资源包共402个文件含164个npy格式多模态样本数据、117个txt日志与配置说明、66个zbak备份文件、15个核心Python训练/推理脚本、12个csv真实故障数据集如machine_temperature_system_failure、nyc_taxi、ec2_request_latency_system_failure等、11个md操作指南及4个pdf技术文档整体压缩包大小为128.11MB。已有54人学习下载适合具备PyTorch基础并希望深入理解多模态特征对齐、自注意力机制设计与异常评分建模的中高级学习者。用户可直接复现端到端流程从多源数据加载、Transformer编码器定制、跨模态重建损失设计到异常分数生成与可视化评估配套文档详述环境配置、训练调参策略及典型失败案例排错要点。1. 项目缘起当异常检测遇上多模态与Transformer最近在做一个工业质检相关的项目客户的需求很明确产线上摄像头拍到的图像、传感器采集的时序信号、以及设备日志里的文本信息需要整合起来判断设备是否即将发生故障。传统的单模态方法比如只用图像做缺陷识别或者只用振动信号做频谱分析经常“看走眼”。比如图像上有个小划痕但传感器数据一切正常这算不算异常或者传感器数据有个尖峰但图像和日志都没问题这又是不是误报这种割裂的判断方式不仅准确率上不去而且虚警率高让运维人员疲于奔命。这正是多模态异常检测要解决的核心痛点。它试图模仿人类专家的综合判断能力——我们判断一个设备是否异常会同时看它的“长相”视觉、听它的“声音”听觉/振动、读它的“病历”文本日志。而Transformer架构自从在NLP领域大放异彩后其强大的序列建模和全局注意力机制让它成为处理这种异构、非对齐多模态数据的理想骨架。它不像CNN那样受限于局部感受野也不像RNN那样难以并行处理长序列它能同时“关注”来自不同模态、不同时间点的所有信息片段并学习它们之间复杂的关联。这个项目就是一次将Transformer架构系统性地应用于多模态异常检测的实践。我会带你从零开始搭建一个能够融合图像、时序、文本数据的检测系统并提供一个完整的、可直接用于训练和评估的数据集与操作指南。无论你是想复现一个原型还是希望深入理解多模态Transformer的工程细节这篇文章都能给你一份清晰的“地图”。2. 核心架构设计如何让Transformer“看懂”图像、时序和文本多模态异常检测系统的核心挑战在于“对齐”与“融合”。图像是空间网格数据时序信号是一维序列文本是离散符号序列它们的维度、尺度和语义空间完全不同。直接扔给一个标准的Transformer是行不通的。我们的架构设计需要解决三个关键问题模态特异性编码、跨模态交互和异常分数生成。2.1 模态特异性编码器为每种数据“翻译”成Transformer能懂的语言Transformer的输入是一系列向量Token。我们的第一步就是把三种模态的原始数据分别“翻译”成这种向量序列。1. 视觉编码器处理图像对于工业图像如产品表面、设备状态图我们通常不直接使用原始的Vision TransformerViT将图像分割成Patch。在异常检测场景下我们更关心纹理、边缘等局部特征以及它们与正常模式的细微偏差。一个更有效的做法是使用一个轻量级的卷积神经网络CNN作为特征提取器例如ResNet-18或EfficientNet的早期层。操作将输入图像例如224x224通过CNN backbone得到一组空间特征图例如7x7x512。然后将这个特征图在空间维度上展平得到49个512维的特征向量。每个向量代表图像的一个局部区域。最后我们为这49个向量添加可学习的位置编码Positional Encoding以保留其空间位置信息。这样我们就得到了视觉Token序列[V1, V2, ..., V49]。为什么这样选直接用ViT的Patch方式会丢失很多细粒度细节而这些细节往往是微小异常的关键。CNN的前几层恰好擅长捕捉这种细节。此外使用预训练的CNN backbone在ImageNet上训练进行初始化是一种有效的迁移学习能加速模型收敛。2. 时序编码器处理传感器信号传感器数据如振动、温度、电流通常是长的一维序列。我们使用一维卷积神经网络1D-CNN或轻量级时序Transformer如Informer的编码器部分来提取局部和全局的时序模式。操作假设我们有一段时间窗口内的振动信号长度为L通道数为C如三轴加速度计C3。我们使用一个包含多个1D卷积层和池化层的网络将原始序列下采样并提取特征最终得到一个长度为L’远小于L的D维特征序列。同样为这个序列添加位置编码这里通常是正弦余弦编码因为时序顺序至关重要得到时序Token序列[T1, T2, ..., TL‘]。注意点对于周期性或趋势性明显的信号在输入前进行简单的预处理如去趋势、标准化、频谱分析有时能带来奇效。但在我们的架构中我们希望模型能自己学习这些模式所以预处理通常只做归一化。3. 文本编码器处理日志/描述文本设备日志通常是半结构化的文本如“Motor_A current spike to 5.2A at 2023-10-27 14:30:22”。我们使用一个预训练的语言模型如BERT、RoBERTa的倒数第二层输出作为文本特征。操作对每条日志文本我们添加[CLS]和[SEP]标记然后输入预训练的BERT模型。我们取[CLS]标记对应的隐藏状态作为整个句子的语义概括向量。如果有多条日志我们将它们按时间顺序拼接每条日志的[CLS]向量作为一个Token。这样我们就得到了文本Token序列[X_CLS1, X_CLS2, ...]。同样需要添加位置编码通常是可学习的因为日志间的顺序关系不如时序信号严格。技巧对于工业领域如果能有领域相关的文本如维修手册、故障描述继续预训练一下BERT效果会更好。这被称为领域自适应Domain Adaptation。2.2 跨模态Transformer融合层建立模态间的“对话”机制现在我们有了三组Token序列视觉TokenV、时序TokenT、文本TokenX。简单的做法是将它们拼接成一个长序列[V; T; X]然后输入一个标准的Transformer编码器。但这存在一个问题模型在自注意力机制中需要学习所有Token两两之间的关系计算复杂度是O((NvNtNx)^2)其中N是各模态Token数。当Token总数很大时这非常消耗资源。我们采用一种更高效的分层融合策略模态内自注意力首先让视觉Token自己内部做一次自注意力一个Transformer块时序Token和文本Token也各自进行。这一步的目的是让每种模态先“整理好自己的思路”强化模态内的特征表示。跨模态交叉注意力这是关键。我们引入可学习的融合Token例如[Fusion]。这个Token会分别去“询问”其他模态。[Fusion]作为Query视觉Token序列作为Key和Value进行一次交叉注意力计算得到融合了视觉信息的特征。同样[Fusion]再作为Query到时序Token和文本Token中分别做交叉注意力整合信息。也可以设计更复杂的交互比如让视觉Token和时序Token直接做交叉注意力如果它们物理上对齐比如某个时刻的图像对应某个时刻的传感器读数。最终表征经过几层这样的分层融合模态内自注意力 以[Fusion]为中心的跨模态注意力后[Fusion]Token的最终隐藏状态就包含了所有模态的联合信息作为整个多模态样本的全局表征h_fusion。为什么用可学习融合Token它就像一个“会议主持人”主动去收集和总结各方各模态的信息比让所有参会者所有Token自由混乱讨论全连接自注意力更高效、更聚焦目标异常检测。2.3 异常评分头从融合特征到异常分数得到全局融合表征h_fusion后我们需要输出一个异常分数。这不是一个简单的二分类问题正常/异常因为异常的程度和类型多种多样。我们采用两种并行的策略重构误差路径让模型学习重构输入。具体来说我们从h_fusion解码试图分别重构出各模态的特征或原始数据的某种表示。例如通过一个反卷积网络重构图像特征通过一个1D反卷积网络重构时序特征。异常样本的重构误差通常会显著高于正常样本。我们将各模态的重构误差如MSE损失加权求和作为异常分数的一部分。判别路径同时我们将h_fusion输入一个简单的多层感知机MLP直接预测一个0到1之间的异常概率。这个路径让模型学习更直接的判别边界。最终的异常分数是重构误差分数和判别概率的加权组合或者更常见的在训练时使用多任务损失重构损失 判别损失在推理时使用判别器的输出概率作为分数。损失函数设计L_total λ_recon * (L_recon_image L_recon_time L_recon_text) λ_cls * L_cls其中L_cls是二元交叉熵损失。λ_recon和λ_cls是超参数需要根据数据集调整。通常重构损失对于发现未知类型的异常即训练集中未出现过的异常更有帮助。3. 数据集构建打造一个真实可用的多模态异常检测基准“巧妇难为无米之炊”。多模态异常检测的研究和应用一直受限于高质量、公开的数据集。很多论文使用合成的或过于简单的数据集导致方法在实际场景中水土不服。为此我构建并开源了一个专注于工业场景的多模态异常检测数据集MMAD-Industrial。3.1 数据采集与仿真策略完全在真实工业产线上收集涵盖正常和各种故障的多模态数据成本极高、周期极长。我们采用“真实背景仿真异常”的策略来构建数据集在保证数据真实性的同时覆盖尽可能多的异常类型。视觉数据我们在一个协作机器人装配工作站和一条小型PCB检测线上采集了大量正常状态下的高清图像10,000张。对于异常我们模拟了常见的工业缺陷表面缺陷使用图像处理库OpenCV在正常图像上添加划痕、污渍、腐蚀斑点。装配错误使用3D渲染软件Blender基于真实的CAD模型渲染出零件漏装、错装、歪斜的图像并利用风格迁移技术使其与真实拍摄的背景融合。物体存在/缺失直接在真实图像序列中人工移除或添加某个组件。时序数据我们连接了产线上电机、气缸的振动传感器和电流传感器采集了正常作业周期内的时序信号。异常时序数据通过以下方式生成注入已知故障模式在正常信号上叠加特定频率的共振波形模拟不平衡、冲击波形模拟撞击、趋势性漂移模拟磨损。物理仿真使用MATLAB/Simulink搭建简单的电机-负载模型仿真轴承损坏、齿轮断齿等故障下的振动信号。文本数据文本日志是最难仿真的。我们与工厂的MES系统工程师合作导出了数月内的设备运行日志。然后根据故障仿真事件我们人工编写了对应的异常日志条目使其符合真实日志的格式和用语习惯。例如正常日志“2023-10-27 14:30:00, Station_1, Cycle_Complete, Status_OK”异常日志“2023-10-27 14:30:05, Station_1, Motor_A, Current_Overload, Value5.3A, Threshold4.5A”3.2 数据对齐与标注多模态数据对齐是一个巨大挑战。我们的策略是以“事件”为中心进行弱对齐。时间对齐所有数据图像帧、传感器采样点、日志条目都打上高精度的时间戳同步时钟。我们将连续时间划分为固定的时间窗口例如5秒一个窗口。对于一个时间窗口我们可能有多张图像、一段连续的时序信号和若干条日志。它们共同描述了这个窗口内系统的状态。样本构成一个数据样本就是一个时间窗口内的所有数据。如果某个模态在窗口内缺失比如该窗口没有产生日志我们用一个特殊的[MASK]Token来填充该模态的输入。标注每个时间窗口有一个全局的异常标签0正常1异常。此外我们还提供了模态级别的异常标注可选指明异常主要来源于哪个或哪些模态。这对于模型可解释性分析非常有用。3.3 数据集格式与目录结构我们将数据集设计为易于使用的格式主要使用JSON和NumPy存储。MMAD-Industrial/ ├── README.md ├── license.txt (Apache License 2.0) ├── meta_info.json # 数据集元信息如样本数、模态、传感器列表 ├── samples/ # 所有样本数据 │ ├── sample_0001/ │ │ ├── timestamp.txt │ │ ├── image.png │ │ ├── time_series.npy # 形状为 (time_steps, channels) │ │ └── log.json # 日志列表每条包含时间、内容 │ ├── sample_0002/ │ └── ... ├── annotations/ │ ├── global_labels.csv # 列sample_id, is_anomaly │ └── modality_labels.csv # 列sample_id, image_anomaly, ts_anomaly, text_anomaly └── splits/ ├── train_normal.txt # 训练集仅正常样本ID列表 ├── test.txt # 测试集ID列表 └── test_labels.csv # 测试集标签用于评估关于Apache License 2.0我们选择此协议开源数据集意味着使用者可以自由地使用、修改、分发该数据集包括商业用途只需保留原始版权声明和许可文本。这极大降低了研究和应用的门槛。4. 实操指南从环境搭建到训练评估理论说了这么多现在我们来动手实现。我将以PyTorch为例分步讲解核心代码和操作。4.1 环境准备与依赖安装首先创建一个干净的Python环境推荐使用conda然后安装核心依赖。# 创建并激活环境 conda create -n mmad python3.9 conda activate mmad # 安装PyTorch (请根据你的CUDA版本访问官网选择命令) # 例如对于CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装其他依赖 pip install transformers # 用于文本编码器BERT pip install timm # 用于视觉编码器预训练CNN模型 pip install scikit-learn pandas numpy opencv-python matplotlib pip install einops # 爱因斯坦求和约定便于张量操作4.2 核心模型代码拆解我们构建一个名为MultimodalAnomalyTransformer的类。import torch import torch.nn as nn from transformers import AutoModel, AutoTokenizer import timm import einops class MultimodalAnomalyTransformer(nn.Module): def __init__(self, config): super().__init__() self.config config # 1. 模态特异性编码器 # 视觉编码器 self.visual_encoder timm.create_model(resnet18, pretrainedTrue, features_onlyTrue) self.visual_proj nn.Linear(512, config.hidden_dim) # 将CNN特征投影到统一维度 # 时序编码器 self.temporal_encoder nn.Sequential( nn.Conv1d(in_channels3, out_channels64, kernel_size7, padding3), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2), # ... 更多层 nn.AdaptiveAvgPool1d(1) # 全局池化或输出序列 ) self.temporal_proj nn.Linear(64, config.hidden_dim) # 文本编码器 self.text_tokenizer AutoTokenizer.from_pretrained(bert-base-uncased) self.text_encoder AutoModel.from_pretrained(bert-base-uncased) # 冻结BERT的大部分参数只微调顶层 for param in self.text_encoder.parameters(): param.requires_grad False for param in self.text_encoder.pooler.parameters(): param.requires_grad True self.text_proj nn.Linear(768, config.hidden_dim) # BERT隐藏层维度是768 # 2. 可学习融合Token和位置编码 self.fusion_token nn.Parameter(torch.randn(1, 1, config.hidden_dim)) self.modal_type_embedding nn.Embedding(3, config.hidden_dim) # 区分三种模态 self.pos_encoder PositionalEncoding(config.hidden_dim) # 自定义的位置编码层 # 3. 跨模态Transformer层 encoder_layer nn.TransformerEncoderLayer( d_modelconfig.hidden_dim, nheadconfig.num_heads, dim_feedforwardconfig.ffn_dim, dropoutconfig.dropout, batch_firstTrue # 重要PyTorch 1.9 支持 ) self.transformer_encoder nn.TransformerEncoder(encoder_layer, num_layersconfig.num_layers) # 4. 解码器用于重构 self.image_decoder ... # 例如转置卷积网络 self.time_decoder ... # 例如1D转置卷积网络 # 文本重构比较困难通常重构[CLS]向量的语义或者跳过 # 5. 异常评分头 self.anomaly_scorer nn.Sequential( nn.Linear(config.hidden_dim, config.hidden_dim // 2), nn.ReLU(), nn.Dropout(config.dropout), nn.Linear(config.hidden_dim // 2, 1), nn.Sigmoid() ) def forward(self, images, time_series, text_strings): batch_size images.size(0) # 编码各模态 visual_tokens self._encode_visual(images) # [B, Nv, D] temporal_tokens self._encode_temporal(time_series) # [B, Nt, D] text_tokens self._encode_text(text_strings) # [B, Nx, D] # 添加模态类型嵌入和位置编码 visual_tokens visual_tokens self.modal_type_embedding(torch.zeros(batch_size, visual_tokens.size(1), dtypetorch.long, deviceimages.device)) # ... 同样为temporal_tokens和text_tokens添加类型嵌入值为1和2 # 合并所有Token并添加可学习融合Token fusion_tokens self.fusion_token.expand(batch_size, -1, -1) all_tokens torch.cat([fusion_tokens, visual_tokens, temporal_tokens, text_tokens], dim1) # 添加位置编码 all_tokens self.pos_encoder(all_tokens) # 通过Transformer编码器 encoded self.transformer_encoder(all_tokens) # [B, 1NvNtNx, D] # 取出融合Token的输出 fusion_output encoded[:, 0, :] # [B, D] # 异常分数 anomaly_score self.anomaly_scorer(fusion_output).squeeze(-1) # [B] # 重构训练时使用 if self.training: # 从encoded中取出各模态对应的部分进行解码重构 recon_loss self._compute_recon_loss(encoded, visual_tokens, temporal_tokens, text_tokens) return anomaly_score, recon_loss else: return anomaly_score def _encode_visual(self, x): # 使用CNN提取特征并投影 features self.visual_encoder(x)[-1] # 取最后一层特征图 B, C, H, W features.shape features einops.rearrange(features, b c h w - b (h w) c) tokens self.visual_proj(features) return tokens def _encode_text(self, texts): # 分词并编码 inputs self.text_tokenizer(texts, paddingTrue, truncationTrue, return_tensorspt).to(texts.device) with torch.no_grad(): # 因为冻结了所以用no_grad outputs self.text_encoder(**inputs) # 取最后一层隐藏状态也可以用池化层输出 last_hidden_state outputs.last_hidden_state # [B, SeqLen, 768] cls_token last_hidden_state[:, 0, :] # [B, 768] # 我们这里简单地将[CLS]作为整个文本的单一Token tokens self.text_proj(cls_token).unsqueeze(1) # [B, 1, D] return tokens4.3 训练流程与关键技巧训练这个多模态模型需要精心设计流程尤其是数据加载和损失平衡。数据加载器需要自定义一个Dataset类从我们构建的MMAD-Industrial数据集中读取对齐的多模态样本。关键点在于处理不同模态数据的不同长度和缺失情况。训练策略预训练阶段可选但推荐由于正常样本远多于异常样本我们可以先用仅正常样本训练模型的重构能力。目标是最小化重构损失。这相当于让模型学习“正常世界”的分布。微调阶段加入带标签的异常样本通常很少使用多任务损失L_total进行训练。此时判别损失会引导模型学习区分正常与异常而重构损失则起到正则化的作用防止模型过拟合到有限的异常样本上。损失权重调整λ_recon和λ_cls需要仔细调整。一个经验是在预训练阶段λ_cls0在微调阶段初期可以设置λ_recon较大让模型保持好的重构能力后期逐渐增大λ_cls的权重强化判别能力。一个常见的坑文本模态的梯度爆炸。由于我们冻结了BERT的大部分参数只训练顶层的投影层和后续网络梯度流相对稳定。但如果解冻太多层由于文本输入的离散性和BERT的巨大参数量很容易导致训练不稳定。建议始终冻结BERT的大部分层或者使用非常小的学习率如1e-5来微调它。4.4 评估与可视化不仅仅是AUC模型训练好后我们需要评估其性能。异常检测常用的指标是ROC曲线下面积AUC和精确率-召回率曲线下面积AP。在测试集上计算模型输出的异常分数然后与真实标签对比即可得到。但更重要的是可解释性模型为什么认为某个样本是异常的是哪个模态贡献最大注意力可视化提取Transformer融合层中[Fusion]Token对其他模态Token的注意力权重。可以画出热力图显示在做出异常判断时模型更“关注”图像的哪个区域、时序的哪个时间点、日志的哪条记录。模态消融实验在推理时依次屏蔽掉某个模态的输入用零向量或均值填充观察异常分数的变化。分数下降最多的模态就是对该异常判断贡献最大的模态。这能帮助我们理解异常的根本原因。# 简单的评估循环示例 def evaluate(model, dataloader, device): model.eval() all_scores [] all_labels [] with torch.no_grad(): for batch in dataloader: images, time_series, texts, labels batch images, time_series, labels images.to(device), time_series.to(device), labels.to(device) # 注意文本在模型内部处理这里texts是字符串列表 scores model(images, time_series, texts) all_scores.extend(scores.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) # 计算AUC from sklearn.metrics import roc_auc_score auc roc_auc_score(all_labels, all_scores) return auc, all_scores, all_labels5. 避坑指南与性能优化在实际部署和优化这个系统的过程中我踩过不少坑这里总结几个关键点。5.1 模态缺失与异步数据处理真实场景中模态缺失是常态。摄像头可能短暂故障传感器可能掉线日志可能延迟。我们的系统必须鲁棒。解决方案在数据预处理和模型前向传播中都要处理缺失。在数据层面对于缺失的模态我们用该模态在训练集上的统计均值对于时序或一个全零图像/[MASK]文本Token来填充。在模型层面我们在输入Transformer之前为每个Token添加一个“模态存在”的嵌入向量让模型知道哪些信息是可靠的。5.2 计算效率与轻量化多模态Transformer模型参数量大计算开销高在边缘设备部署困难。优化策略模态编码器轻量化使用MobileNetV3等轻量CNN代替ResNet使用更浅的1D CNN处理时序。Transformer层优化减少Transformer的层数和头数num_layers,num_heads。使用线性注意力Linear Attention等近似机制替代标准的Softmax注意力将计算复杂度从O(N²)降至O(N)。知识蒸馏训练一个庞大的教师模型然后用它来指导一个轻量级学生模型的学习在几乎不损失精度的情况下大幅减小模型尺寸。动态推理并非所有样本都需要所有模态和全部计算。可以设计一个轻量级的“门控”网络先快速判断异常可能性只有高可疑样本才启动完整的多模态深度分析。5.3 负样本异常不足与数据增强工业场景下异常样本极其稀少且类型未知。应对方法无监督/自监督预训练正如之前提到的用海量正常数据预训练重构任务让模型深刻理解“正常”是什么。任何偏离“正常”的模式都会被检测为异常。合成异常我们构建数据集的方法就是合成异常。在训练中也可以在线on-the-fly进行数据增强例如对正常图像随机添加噪声、遮挡对正常时序信号注入小幅扰动然后将这些“轻微异常”的样本也视为正常或者赋予一个较低的异常标签这可以提高模型对微小异常的敏感度。利用跨域数据如果同一类设备在其他工厂有异常数据可以尝试进行领域自适应Domain Adaptation将知识迁移过来。5.4 阈值选择与在线学习模型输出的是0到1之间的异常分数需要一个阈值来判断是否报警。阈值设定在验证集或仅含正常样本的测试集上计算所有正常样本的异常分数分布选择一个分数使得例如99%的正常样本分数低于它这个分数就可以作为初始阈值。在实际运行中这个阈值需要根据误报率False Positive Rate进行动态调整。在线学习与适应产线的正常模式可能会缓慢漂移例如设备老化、季节变化。模型需要能够适应这种变化。可以定期如每天用最近一段时间内被确认为正常的样本对模型进行在线微调只更新少量参数或使用很小的学习率使其“正常”的概念与时俱进。但要小心避免将新出现的、尚未被发现的异常模式学进去。6. 总结与展望从原型到产线实现一个基于Transformer的多模态异常检测系统从架构设计、数据集构建到代码实现是一个系统工程。它不仅仅是堆叠几个SOTA模型更需要深入理解业务场景、数据特性和工程约束。这套系统的优势在于其强大的信息融合能力和对复杂异常模式的捕捉潜力。但它也带来了复杂性需要对齐的多模态数据、较高的计算需求、以及更复杂的调参过程。在决定采用此类方案前务必评估其投入产出比。对于某些简单场景单模态检测器组合规则引擎可能就已经足够高效可靠。未来这个方向还有不少值得探索的点。例如如何实现更灵活的、支持任意模态增减的架构如何利用大语言模型LLM的强大语义理解能力将文本日志甚至语音指令更好地融入系统如何在模型本身中内置更强的可解释性模块让运维人员不仅能收到报警还能立刻知道“可能是电机A的轴承磨损导致振动异常建议检查”。这个项目提供的代码和数据集是一个起点。工业世界的异常千变万化最好的系统永远是那个最懂你具体产线、具体设备的系统。希望这份指南能帮你打下坚实的基础在实际项目中少走弯路构建出真正智能、可靠的“设备健康守护者”。本文还有配套的精品资源点击获取