桥梁健康监测数据实战:传感器时间序列清洗与特征工程
简介一份桥梁结构健康监测传感器数据集及配套代码包面向桥梁工程、数据科学、机器学习方向的研究人员与软件开发者适用于结构健康分析、异常检测和算法建模等场景。包内共5个文件以CSV核心数据文件为主辅以MD说明文档、HTML页面展示及代码环境配置整体压缩包仅9KB便于快速下载使用。数据涵盖2023年10月1日8:00至12:45某跨江桥梁5个关键部位传感器的连续监测记录包含振动、温度、应力等关键参数无缺失值并附异常数据说明和预设关联模式可直接用于工程诊断和算法开发。研究者可基于振动分布、应力极值及温度相关性开展桥梁动态响应分析、承载能力评估与结构寿命预测开发者则能借助这些特征与模式完善监测软件算法校验系统检测逻辑。目前已有136人学习使用作为小型而完整的真实监测数据样本适合作为教学实验与科研项目的起点。1. 桥梁传感器数据集从一堆 csv 到能判断结构状态的分析起点做桥梁结构健康监测的朋友应该都有同感桥上的加速度计和应变计跑了一年攒下来上百 G 原始数据但真正能回答“桥现在到底行不行”的信号可能只占极小一部分。桥梁传感器数据集通常就是这类监测系统产出的多通道时序数据配套项目代码解决读取、清洗、特征提取和基线建模。它要解决的不是采集问题而是把长年累月的振动记录变成可训练、可对比、可报警的样本集。适合手里有一批监测数据不知道怎么下手的工程师也适合做时序异常检测、想找真实场景而不是仿真数据的研究者。2. 读懂一套桥梁传感器数据集通道布局、采样率与时间戳对齐这类数据集配套的项目代码不管仓库怎么组织核心通常都绕不开三块读数据、清洗数据、提特征。而第一步“读数据”里面真正花时间的不是read_csv一行代码而是理解通道名、采样率和时间戳之间的关系。这三个东西搞错任何一个后面所有统计指标和模型输出都会变成另一套故事。下面按照我拿到这类数据集时的检查顺序展开。2.1 传感器通道与布局先搞清楚每个列名背后的物理位置拿到数据集第一件事不是跑模型而是打开通道说明表。桥梁监测系统里最常见的传感器类型是加速度计、应变计、温度计和位移计它们的采样率、物理含义和分析用途完全不一样。通道类型典型采样率主要用途三轴加速度50 ~ 200 Hz模态频率、振动幅值、异常冲击应变片1 ~ 20 Hz局部应力集中、疲劳累计温度0.1 ~ 1 Hz环境补偿、频率漂移对照位移/倾角1 ~ 10 Hz整体变形趋势、支座沉降加速度通道是分析主力采样率最高数据量最大。通道命名通常带着物理位置信息比如ACC_Z_07表示 7 号测点的竖向加速度ACC_X_03表示 3 号测点的横向加速度。位置信息不是摆设桥跨中部测点和桥墩顶部测点的振动特征差异非常大前者以弯曲模态为主后者可能带着支座和墩柱的局部响应。如果把不同位置的通道当作同质特征直接拼接后面训练出来的模型在物理上就没有依据。我一般会先把通道列表打印出来和桥梁结构图对照一遍确认每个测点对应主梁、桥墩还是拉索。这一步看着不起眼但决定了后面能不能解释“为什么这个特征在这个桥上有效、在另一座桥上失效”。通道布局是这类数据集自带的一种监督信息丢掉它等于把一半的项目经验扔了。2.2 用 Pandas 读取并对齐到统一时间轴最小可用读取流程配套项目代码里一般会有一个base_loader.py或类似的加载脚本作用是把散落的单通道文件变成一张对齐后的宽表。下面这段逻辑是这类脚本最常见的内核。import pandas as pd import numpy as np # 读取单通道加速度数据 df pd.read_csv( acc_z_07.csv, parse_dates[timestamp], index_coltimestamp, ) print(f数据量: {len(df):,}) print(f时间范围: {df.index.min()} - {df.index.max()}) print(f缺失值数量: {df.isna().sum().sum()}) # 检查采样间隔是否等间隔 diff df.index.to_series().diff().dt.total_seconds().dropna() print(diff.value_counts().head())这里先看数据量和时间范围再统计缺失值是为了快速判断采集过程有没有掉线。数据量如果远小于“时长乘采样率”的理论值说明仪器有过断记后面的预处理策略就要围绕分段来做。时间戳等间隔检查是关键一步。value_counts()能直接告诉我们最常见的采样间隔是多少秒。如果众数是 0.01采样率就是 100 Hz如果出现多个接近的间隔说明时间戳本身不均匀需要重采样对齐。多通道对齐的常见做法是把所有通道重采样到同一个时间网格fs 100 # 单位 Hz以数据推断结果为准 rule f{1000 / fs:.0f}ms # 100Hz 对应 10ms aligned df.resample(rule).nearest().dropna()resample(rule).nearest()的含义是以固定时间间隔建立网格每个网格点取离它最近的原始采样值。它适用于单点轻微抖动或少量缺失的情况不会像线性插值那样在数据里造出新数值。注意rule的计算依赖采样率能整除 1000如果采样率是 128 Hz 这类非整毫秒值就建议保留原始时间戳、按需重采样而不是硬凑规则。如果配套代码里提供了批量加载逻辑最终往往是把所有通道concat成一张宽表列名保留通道名行索引统一为对齐后的时间戳。这样后面做窗口切分和特征提取时才能做到“同一时刻、所有通道对齐”。3. 数据清洗是这类数据集的关键去趋势、尖峰剔除与缺失段处理桥梁监测数据是所有时序数据里最不干净的类型之一。仪器长期暴露在室外温度变化、雷击、电磁干扰、施工敲击都会在信号里留下痕迹。这一段讲三个最常见的清洗动作去低频漂移、剔尖峰、处理缺失段。顺序很重要建议先做高通滤波再切缺失段最后剔除尖峰理由在 5.5 节统一说明。3.1 为什么要先做去趋势和滤波环境漂移不是损伤信号原始加速度数据里通常会有一条非常明显的长周期漂移时间尺度从几十秒到几小时。它来自温度变化引起的传感器零点漂移、电缆热电势以及仪器本身的直流偏置。很多人第一次看到这种波形会误以为桥梁在缓慢变形实际上和结构振动没有关系。处理上最常用的是 Butterworth 高通滤波器截止频率我一般取 0.1 Hz。桥梁一阶竖向频率通常在 0.5 Hz 以上0.1 Hz 的高通不会碰结构模态却能干净地去掉温度级漂移。from scipy.signal import butter, filtfilt def highpass(x, fs, cutoff0.1): 零相位高通滤波cutoff 单位 Hz sos butter(4, cutoff, fsfs, btypehigh, outputsos) return filtfilt(sos, x)这里用sos形式而不是(b, a)形式是因为高阶 Butterworth 用(b, a)时数值稳定性差sos格式更稳。filtfilt是零相位滤波先正向再反向各过一次输出不会有相位偏移。对于结构模态分析来说相位偏移会直接影响频率估计所以不能用lfilter替代。截止频率的选取要结合数据实际情况。如果频谱显示 0.1~0.5 Hz 之间有明显的结构模态比如某些超大跨桥梁的低阶模态确实在这个区间那就要把截止频率降下来或者改用带通。这个参数不要照抄要看数据再定。3.2 尖峰识别与剔除一个基于滚动中位数的稳定实现尖峰是桥梁数据里最常见的“脏数据”波形上突然冒出一个远高于正常幅度的孤立点持续时间只有几个采样点。它可能来自电磁干扰、采集卡瞬间故障也可能是桥面施工敲击。如果不处理RMS、峰值因子这些特征都会被瞬间拉高直接触发误报警。我常用的尖峰剔除方法是滚动中位数加绝对偏差阈值对孤立脉冲非常有效。def remove_spikes(x, fs, window_s1.0, thresh10.0): 滚动中位数法剔除尖峰。 x: 一维加速度数组 fs: 采样率 window_s: 中位数窗口时长秒 thresh: 偏差阈值单位是 MAD 换算后的标准差 win max(5, int(fs * window_s) | 1) # 保证奇数窗口 med pd.Series(x).rolling(win, centerTrue).median().to_numpy() mad np.median(np.abs(x - med)) 1e-9 dev np.abs(x - med) / (1.4826 * mad) mask dev thresh out x.copy() out[mask] med[mask] return out, maskMAD中位数绝对偏差乘以 1.4826 之后可以近似换算成标准差尺度这样thresh10就表示“偏离局部中位数 10 个标准差才判为尖峰”对正常振动数据来说误伤率很低。窗口长度取 1 秒表示每个点用前后共 1 秒的邻域做基准短到不会把真实的秒级冲击抹平长到足够建立一个稳定基线。这个方法的边界情况是密集冲击段。比如大量重车连续通过时波形本身就是一堆高幅值冲击中位数会被抬高尖峰反而不容易被识别出来。这种场景不要试图用一个全局阈值解决正确做法是先按第 4 章的窗口切分算出 RMS把 RMS 异常高的窗口单独拿出来人工审查再决定要不要用尖峰剔除。先粗筛、再精修这是这类数据清洗里比较省心的策略。3.3 缺失段与仪器失效段宁可截断也不要硬插值桥梁监测系统长年运行掉线、存储写满、网络抖动都可能导致几小时甚至几天的数据空白。处理长缺失段的最常见错误是用线性插值把空白填上让时间轴连续。插值之后从波形上看确实连贯了但频谱里会出现一个由斜坡造出来的低频成分后患无穷。我的处理原则是超过 1 秒的间隔一律视为断点按断点把数据切成若干段只保留足够长的段用于后续分析。def split_by_gaps(x, fs, max_gap_s1.0, min_seg_s60.0): 按时间间隔切分数据段丢弃短段。 x: 带 DatetimeIndex 的 Series fs: 采样率 max_gap_s: 超过该间隔视为断点 min_seg_s: 段长小于该值则丢弃 ts x.index.to_series() gap_s ts.diff().dt.total_seconds() gap_s.iloc[0] 0.0 split_idx np.where(gap_s.to_numpy() max_gap_s)[0] segments np.split(x.to_numpy(), split_idx) return [seg for seg in segments if len(seg) min_seg_s * fs]min_seg_s怎么定取决于第 4 章特征提取的最小窗口长度。如果滑动窗口用 60 秒那么每段至少要有 60 秒数据才能算出一个有效样本如果做 10 分钟级的长期趋势分析就要求段长不低于 10 分钟。宁缺毋滥短段里统计量方差太大放进样本集只会变成噪声。注意切段之前做高通滤波切段之后做尖峰剔除。先滤波可以把段边界附近的瞬态响应和真实趋势分开后剔除可以避免尖峰检测窗口跨到断点两侧、把两个不相干的数据段拖进同一个局部中位数里。4. 从振动信号到可训练样本窗口切分与特征提取数据清洗完之后下一步是把连续时间序列切成样本。这一步直接决定了模型看到的是什么。桥梁监测的数据标注非常稀疏正常段占 99% 以上异常事件可能是几秒钟的撞击、持续几小时的支座卡滞或者跨度几个月的频率缓慢漂移。窗口切多大、特征提哪些要按目标事件的时间尺度来定。4.1 滑动窗口切分的两个参数窗口长度与重叠率滑动窗口切分是所有时序特征工程的基础。窗口长度决定了一个样本覆盖多长的时间范围重叠率决定相邻样本之间的信息重复程度。def sliding_windows(x, win_s, hop_s, fs): 将一维信号切成二维窗口矩阵。 x: 预处理后的一维数组 win_s: 窗口长度秒 hop_s: 步长秒 fs: 采样率 win int(win_s * fs) hop int(hop_s * fs) n (len(x) - win) // hop 1 idx np.arange(n)[:, None] * hop np.arange(win)[None, :] return x[idx]这个用行索引广播实现的版本比as_strided更直观内存效率差一些但可读性好。n的计算保证不会越界最后一个不满窗口的尾部直接丢弃。窗口长度怎么选做短时异常事件检测比如重车冲击、支座异响30~60 秒足够做长期状态跟踪比如模态频率随温度的季节性漂移10 分钟甚至更长的窗口才能把频谱分辨率做上去。重叠率一般给 50%~75%重叠的目的是不让瞬态事件恰好被窗口边界切掉同时也能增加样本数量。但要注意重叠窗口之间样本不独立如果用这些样本做监督训练并划分训练集和验证集严格来说需要按时间段切分而不是按样本随机切分否则会高估模型效果。4.2 时域特征RMS、峰值因子与峭度的工程含义窗口切好之后特征提取就顺理成章了。时域特征计算简单、物理含义清晰是这类数据集第一梯队的选择。def extract_time_features(win): 从单个窗口提取时域统计特征 rms np.sqrt(np.mean(win**2)) peak np.max(np.abs(win)) std win.std() kurt np.mean((win - win.mean())**4) / (std**4 1e-12) return { rms: rms, # 振动能量水平 crest_factor: peak / (rms 1e-9), # 峰值因子对冲击敏感 kurtosis: kurt, # 峭度正态时序约等于 3 }RMS 反映窗口内振动能量日常交通下的 RMS 基本稳定一旦出现异常它是最直观的指示。峰值因子是峰值除以 RMS正常平稳振动的峰值因子通常在 3~5冲击型信号会明显抬高。峭度是四阶统计量平稳随机信号的峭度接近 3冲击性事件会让它迅速跳到 10 以上。这三个特征配合起来能区分很多情况RMS 高且峭度高大概率是真实冲击RMS 高但峭度正常可能是整体振动水平上升RMS 正常但峭度偶尔飙高往往是单个尖峰没除干净这时回头查清洗环节而不是怀疑桥梁出了问题。只看特征名不看物理含义特征工程就变成了黑匣子报警了都不知道怎么跟业主解释。4.3 频域特征用 Welch 法稳定地跟踪主频漂移桥梁结构损伤的经典征兆之一是模态频率下降。跟踪主频需要稳定的频谱估计。直接用整段数据做 FFT 是新手最容易踩的坑原因在第 5.5 节展开。工程上更可靠的做法是 Welch 平均周期图把长信号分成多段、分别做 FFT 再平均谱线平滑得多。from scipy.signal import welch def dominant_freq(x, fs, f_low0.5, f_high5.0): 在指定频带内找主频。 x: 单个窗口数据 fs: 采样率 f_low, f_high: 搜索频带单位 Hz f, psd welch(x, fsfs, npersegmin(len(x), 4096)) band (f f_low) (f f_high) f_b f[band] psd_b psd[band] return f_b[np.argmax(psd_b)]nperseg4096时频率分辨率约等于 fs/4096。按 100 Hz 采样率算大约是 0.024 Hz对跟踪 0.1 Hz 级别的频率漂移够用。搜索频带限制在 0.5~5 Hz是为了避开低频漂移残留和高频噪声如果数据来自超大跨桥梁低阶模态可能在 0.2~0.5 Hz这个频带要按实际频谱调整。Welch 的另一个好处是它对非平稳信号更稳。环境激励下的桥梁响应本来就是时变的整段 FFT 相当于把所有时段的频谱叠加平均得到的主峰又宽又平Welch 分段平均相当于对瞬时频谱做了统计平滑主峰位置稳定得多。特征提取这一步输出的是一张“窗口 × 特征”的表后面无论是做阈值报警、孤立森林还是自编码器输入都从这张表开始。5. 桥梁数据的 5 个常见坑现象、原因与排查方法这个领域我踩过的坑比做过的模型还多。下面五条是桥梁传感器数据集处理里最典型的问题每条按现象、原因、解决来写。踩过任何一条基本都能在这里找到原因。5.1 时间戳漂移导致同一时刻各通道对不上现象把两个加速度通道画在同一张图上波形形状看起来相似但互相关系数很低峰值错开几十毫秒甚至几百毫秒。用互相关找滞后峰值不落在零延迟位置。原因不同采集卡各走各的时钟长期暴露在温度变化环境里晶振漂移导致各通道的时间基准不一致。文件里时间戳各自独立不能直接按字符串对齐。解决用一段信噪比高的数据做互相关估计两通道之间的延迟量再对齐。from scipy.signal import correlate # 取中间 10 分钟避免首尾的瞬态干扰 ref channel_a[60000:60000 600 * fs] x channel_b[60000:60000 600 * fs] lag np.argmax(np.abs(correlate(ref, x, modefull))) - (len(ref) - 1) delay_s lag / fs aligned_b np.roll(channel_b, -lag)lag的单位是采样点除以fs就是秒数。如果延迟是固定值np.roll一次对齐即可如果延迟随时间变化说明硬件同步有问题软件补不回来只能换同步方案。判断延迟是否变化的方法按小时分段重复计算互相关滞后观察delay_s是否稳定。5.2 低频漂移被当成结构响应现象原始波形上能看到一条明显的长周期起伏高通滤波前后 RMS 差异巨大频谱里 0 Hz 附近能量高得吓人。有人把这条趋势当成桥梁“缓慢变形”写进报告结论完全失真。原因温度引起传感器零点漂移电缆热电势也会叠加一个缓慢变化的电压信号。它和结构低频响应长相相似物理机制完全不同。解决预处理第一步做高通滤波截止频率按数据实际频谱来定。但要注意应变计通道的直流分量本身是有意义的代表结构的平均应力水平不能一刀切高通。加速度通道默认高通 0.1 Hz应变通道做疲劳分析时保留直流分量。5.3 尖峰把 RMS 特征瞬间拉高触发误报警现象某天凌晨 RMS 突然比日常水平高出十倍报警系统推送了一条“异常振动”通知赶到现场什么也没发生。查看原始波形发现是一个孤立的尖峰点。原因电磁干扰或者采集卡瞬间故障产生尖峰尖峰本身的 RMS 贡献不大但对峰值因子影响极大如果特征管线里没有剔除尖峰这一级阈值报警必然被触发。解决把尖峰剔除放在特征提取之前用 3.2 节的滚动中位数方法。更稳妥的做法是两遍筛查第一遍用粗阈值剔除明显尖峰第二遍在 RMS 异常窗口内做细粒度检查防止真实冲击被误伤。清洗管线一旦定下来就不要轻易改不然前后两批特征的统计口径不一致模型全要重跑。5.4 缺失段插值引入虚假低频现象某通道缺失了 5 小时数据用线性插值补全后频谱里在 0.05~0.2 Hz 范围出现一个非常稳定的谱峰而且这个峰在原始信号里根本不存在。原因几小时的空白被线性插值填上相当于造了一条从空白前值到空白后值的斜坡。斜坡在频域里对应一个很低频率的大幅成分这个成分不是结构响应是插值算法自己造出来的。解决超过 1 秒的缺失段不插值按 3.3 节切段处理。短于 1 秒的微小缺口可以线性插值因为插值长度远小于分析窗口不会形成可见的斜坡。注意一旦把插值后的数据保存成新数据集原始版本又没留备份后面所有分析都会被这个虚假低频污染而且很难追溯。清洗结果建议保留两份一份是切段后的原始净数据一份是特征表给后续复用留后悔药。5.5 整段谱分析让主频读出来一直在跳现象用整段 24 小时数据做 FFT 找主频今天读出来 1.45 Hz明天读出来 1.78 Hz后天又变成 1.32 Hz完全找不到规律。原因桥梁模态频率不是恒定值随温度、交通荷载、风环境变化。整段 FFT 是把一整天的频谱平均在一起主峰摊平变宽取峰值时稍微受一点噪声扰动位置就会跳变。解决改用 Welch 分段平均或者直接按窗口逐步计算主频序列。主频应该是平滑曲线而不是每天一个跳变的点。如果跟踪出来的主频序列仍然波动很大先检查窗口长度是否太短频率分辨率是否足够再检查是不是搜索频带太宽把高频噪声的峰值当成了结构主频。推荐的预处理顺序是先高通滤波再切缺失段最后剔尖峰。这个顺序我试过多种排列目前最稳定原因很简单滤波会产生边界瞬态切段能把这些瞬态留在段外切段之后尖峰检测窗口不会跨过断点中位数基准更干净。# 推荐管线 x_filtered highpass(x_raw, fs, cutoff0.1) segments split_by_gaps(pd.Series(x_filtered, indexts), fs) clean_segments [] for seg in segments: seg_clean, _ remove_spikes(seg, fs) clean_segments.append(seg_clean) # 每个段内部再做滑动窗口特征提取 all_features [] for seg in clean_segments: wins sliding_windows(seg, win_s60, hop_s30, fsfs) for w in wins: all_features.append(extract_time_features(w))6. 把特征送进模型前的一个进阶习惯按桥归一化与跨桥验证6.1 换一座桥就失效先查特征分布而不是调阈值桥梁传感器数据集的模型很难跨桥复用。不同桥的跨度、材料、车流量、传感器型号都不一样同一个特征在不同桥上的绝对量级可能差出数倍。跨桥使用的第一步不是调报警阈值而是先把特征分布对齐。我常用的做法是对每座桥用前半段正常数据拟合基线的均值和标准差然后做 z-score 标准化。def fit_bridge_baseline(features, fit_frac0.5): 用前一半数据估计基线统计量 cut int(len(features) * fit_frac) mu features[:cut].mean(axis0) sd features[:cut].std(axis0) return mu, sd def standardize(features, mu, sd): return (features - mu) / (sd 1e-8) # A 桥拟合、A 桥标准化 mu_a, sd_a fit_bridge_baseline(feats_a) feats_a_std standardize(feats_a, mu_a, sd_a) # B 桥直接用 A 桥的统计量标准化 feats_b_std standardize(feats_b, mu_a, sd_a)fit_frac0.5表示取前一半数据做基线隐含前提是这一半数据不含异常事件。如果这段时间恰好赶上施工或地震基线本身就是坏的因此拟合之前要先对特征做一遍基本异常筛查去掉明显离群点再算均值和标准差。跨桥验证的常见做法是看 B 桥标准化后的误报率。如果误报率很高不要急着调阈值先逐特征检查分布偏移offset (feats_b.mean(axis0) - mu_a) / sd_a print(np.round(offset, 2))哪个特征偏移超过 3就重点排查哪个特征对应的物理量。比如 B 桥的主频比 A 桥低 0.3 Hz那么频率特征天然就不该用 A 桥的基线B 桥的车辆荷载偏重RMS 的整体分布上移标准化之后可能仍然无法对齐。这说明跨桥问题不是统计技巧能彻底解决的模型层面要做适配或者接受“每一座桥独立训练基线”的工程代价。我最早做这类项目时拿到数据就急着切窗、提特征、上模型结果换了一座桥阈值全部失效。后来养成的习惯是任何模型进流程之前先抽几段原始波形画出来看一眼把每座桥的基线统计算一遍再谈建模。这个习惯让我少踩了非常多的坑。希望帮到你。本文还有配套的精品资源点击获取