车辆驾驶行为分析:从三轴传感器到驾驶风格标签的完整链路

发布时间:2026/10/9 22:14:23
车辆驾驶行为分析:从三轴传感器到驾驶风格标签的完整链路 简介这份资源是第七届泰迪杯数据挖掘比赛的车辆驾驶行为分析完整参赛项目面向计算机、人工智能、通信工程等专业的在校学生及数据挖掘初学者可用于课程设计、毕业设计或竞赛复盘。项目在传统驾驶行为评测基础上结合省、市、县级地区的温度、天气、湿度等环境特征建立环境评测模型探究环境因素对车辆行驶节能的影响并将环境因素融入驾驶行为评测体系涉及数据预处理、异常值处理、聚类与评价体系构建等环节。压缩包共10个文件包含5个ipynb交互式分析笔记、2个py脚本、2个md说明文档和1个docx比赛总结整体约1.82MB覆盖从数据清洗到模型落地的完整流程。目前已有167人学习下载读者可获取经过测试运行的源码、比赛总结文档与分步骤分析笔记适合在此基础上修改扩展完成自己的数据挖掘项目。1. 车辆驾驶行为分析赛题从三轴传感器到驾驶风格标签的完整链路第七届泰迪杯数据挖掘比赛里有一道车辆驾驶行为分析题给的是车载传感器采集的三轴加速度、三轴角速度以及车速等时序数据要求参赛者判断当前驾驶行为属于哪一类——急加速、急减速、急转弯、正常行驶甚至进一步推断驾驶风格。很多做数据挖掘的朋友第一次拿到这种数据会懵没有图像、没有文本只有一堆浮点数按时间戳排列特征工程从哪下手标签怎么对齐模型选树模型还是时序网络这篇笔记就把这条链路从头到尾拆一遍包括 Python 项目源码里常见的目录结构、文档说明里容易漏掉的参数细节以及比赛总结里那些“当时要是知道就好了”的坑。适合正在做车辆驾驶行为分析、传感器时序分类或者准备复现类似赛题的人。2. 赛题数据到底长什么样字段含义与标签构造逻辑2.1 三轴传感器数据的物理含义与采样频率车辆驾驶行为分析的数据通常来自车载诊断系统或独立传感器模块。核心字段一般包括acc_x、acc_y、acc_z三轴加速度单位常见为 g 或 m/s²gyro_x、gyro_y、gyro_z三轴角速度单位常见为 °/sspeed车速单位 km/htimestamp时间戳可能是毫秒级或秒级采样频率是第一个要确认的参数。常见做法是 10Hz 到 50Hz如果原始数据是 100Hz直接丢进模型反而会引入大量噪声。我一般会先画一张三轴加速度的时域波形图肉眼确认急加速和急减速的波形差异。急加速时acc_x会出现一个明显的正向尖峰急减速则是负向尖峰急转弯主要体现在gyro_z的突变上。import pandas as pd import matplotlib.pyplot as plt # 读取原始数据假设是 csv 格式 df pd.read_csv(drive_data.csv) # 确认采样频率相邻时间戳差值的中位数 df[ts_diff] df[timestamp].diff() median_interval df[ts_diff].median() print(f中位采样间隔: {median_interval} ms, 对应频率: {1000/median_interval:.1f} Hz) # 画三轴加速度和角速度的时域波形 fig, axes plt.subplots(2, 1, figsize(14, 6), sharexTrue) axes[0].plot(df[timestamp], df[acc_x], labelacc_x) axes[0].plot(df[timestamp], df[acc_y], labelacc_y) axes[0].plot(df[timestamp], df[acc_z], labelacc_z) axes[0].legend(); axes[0].set_ylabel(Acceleration (g)) axes[1].plot(df[timestamp], df[gyro_z], colorr, labelgyro_z) axes[1].legend(); axes[1].set_ylabel(Angular Rate (°/s)) plt.xlabel(Timestamp) plt.show()这段代码先算采样间隔再画波形。参数说明median_interval用来判断是否需要重采样如果中位间隔是 20ms对应 50Hz而模型预期输入是 10Hz就需要做降采样。波形图里如果acc_x的尖峰持续不到 0.5 秒那可能是路面颠簸而不是急加速阈值要相应调整。2.2 标签怎么来滑窗切分与行为段标注赛题给的标签通常有两种形式一种是逐点标签每个时间戳对应一个行为类别另一种是事件段标签只给出某段起止时间属于急加速。逐点标签更常见但原始标签往往有噪声比如急加速段边缘被标成了正常行驶。我一般会先做滑窗切分把连续时序切成固定长度的样本。窗口长度取 2 到 5 秒比较合理步长取窗口的一半做重叠防止边界样本丢失。import numpy as np def sliding_window(data, labels, window_size, step): data: shape (n_samples, n_features) labels: shape (n_samples,) window_size: 窗口内时间点数 step: 滑动步长 X, y [], [] for start in range(0, len(data) - window_size 1, step): end start window_size X.append(data[start:end]) # 取窗口内出现次数最多的标签作为该窗口标签 window_labels labels[start:end] y.append(np.bincount(window_labels).argmax()) return np.array(X), np.array(y) # 假设特征列是 acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z, speed feature_cols [acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z, speed] data df[feature_cols].values labels df[label].values # 50Hz 下 3 秒窗口 150 个点步长 75 X, y sliding_window(data, labels, window_size150, step75) print(f样本数: {X.shape[0]}, 窗口长度: {X.shape[1]}, 特征数: {X.shape[2]})逻辑说明np.bincount(window_labels).argmax()是取窗口内多数标签适合逐点标签有轻微噪声的情况。参数说明window_size太小会导致频域特征提取不准太大则一个窗口里混入多种行为step取窗口一半是经验值既能增加样本量又不会让相邻窗口过于相似。2.3 类别不平衡有多严重先看分布再谈模型车辆驾驶行为数据里正常行驶样本通常占 70% 以上急加速和急减速各占 10% 左右急转弯可能更少。如果不处理模型会倾向于预测正常行驶准确率看着高但召回率惨不忍睹。# 查看类别分布 unique, counts np.unique(y, return_countsTrue) for u, c in zip(unique, counts): print(f类别 {u}: {c} 样本, 占比 {c/len(y)*100:.2f}%)常见做法是先用class_weightbalanced让树模型自动加权或者在滑窗时对少数类做过采样。注意不要直接在原始时序上做 SMOTE那样会破坏时间连续性生成不存在的波形。3. 特征工程从原始波形到可解释的统计量3.1 时域特征均值、方差、过零率与峰峰值时域特征是最快能跑通 baseline 的。对每个窗口的每个轴计算均值、标准差、最大值、最小值、峰峰值、均方根、过零率。这些特征对急加速和急减速的区分度很高。from scipy.stats import skew, kurtosis def extract_time_features(window): window: shape (window_size, n_features) 返回: 一维特征向量 feats [] for i in range(window.shape[1]): axis_data window[:, i] feats.extend([ np.mean(axis_data), np.std(axis_data), np.max(axis_data), np.min(axis_data), np.ptp(axis_data), # 峰峰值 np.sqrt(np.mean(axis_data**2)), # RMS skew(axis_data), kurtosis(axis_data), np.sum(np.diff(np.sign(axis_data)) ! 0) / len(axis_data) # 过零率 ]) return np.array(feats) # 对每个窗口提取特征 X_time np.array([extract_time_features(x) for x in X]) print(f时域特征维度: {X_time.shape})参数说明np.ptp是峰峰值对急加速的冲击很敏感skew和kurtosis描述波形偏度和峭度急减速的负向尖峰会让偏度明显为负。过零率在角速度上意义不大但在加速度上可以反映振动程度。3.2 频域特征FFT 主频与能量占比频域特征能捕捉周期性抖动比如发动机振动和路面噪声。对每个窗口做 FFT取幅值谱的前 10 个峰值频率和对应幅值再算低频段0-5Hz能量占总能量的比例。def extract_freq_features(window, fs50): window: shape (window_size, n_features) fs: 采样频率 feats [] for i in range(window.shape[1]): axis_data window[:, i] - np.mean(window[:, i]) # 去直流 fft_vals np.abs(np.fft.rfft(axis_data)) freqs np.fft.rfftfreq(len(axis_data), d1/fs) # 前 5 个峰值 peak_idx np.argsort(fft_vals)[-5:][::-1] for idx in peak_idx: feats.append(freqs[idx]) feats.append(fft_vals[idx]) # 低频能量占比 low_mask freqs 5 low_energy np.sum(fft_vals[low_mask]**2) total_energy np.sum(fft_vals**2) 1e-8 feats.append(low_energy / total_energy) return np.array(feats) X_freq np.array([extract_freq_features(x) for x in X]) print(f频域特征维度: {X_freq.shape})逻辑说明np.fft.rfft返回实数 FFT 的一半因为输入是实数信号。freqs是对应的频率轴。低频能量占比高说明窗口内主要是缓慢变化急转弯的角速度变化频率通常比急加速更低。3.3 特征拼接与标准化别让量纲毁了模型时域和频域特征拼在一起后量纲差异巨大。均值可能是 0.01RMS 可能是 10不做标准化基于距离的模型直接翻车。from sklearn.preprocessing import StandardScaler X_all np.hstack([X_time, X_freq]) scaler StandardScaler() X_scaled scaler.fit_transform(X_all) # 保存 scaler 供后续推理使用 import joblib joblib.dump(scaler, feature_scaler.pkl) print(f最终特征维度: {X_scaled.shape})注意fit_transform只能在训练集上做验证集和测试集要用transform。我见过有人把全部数据一起标准化导致验证集信息泄露线下分数虚高线上直接崩。4. 模型选型与训练树模型、时序网络与集成策略4.1 为什么先用 LightGBM 跑 baseline车辆驾驶行为分析的特征工程做完后特征维度通常在 100 到 300 之间样本量几千到几万。这种结构化数据梯度提升树LightGBM、XGBoost往往比 LSTM 更快更稳。LightGBM 训练快能输出特征重要性方便回头检查哪些特征在起作用。import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_val, y_train, y_val train_test_split( X_scaled, y, test_size0.2, random_state42, stratifyy ) clf lgb.LGBMClassifier( n_estimators500, learning_rate0.05, num_leaves31, max_depth-1, class_weightbalanced, random_state42 ) clf.fit(X_train, y_train, eval_set[(X_val, y_val)], eval_metricmulti_logloss, callbacks[lgb.early_stopping(50)]) y_pred clf.predict(X_val) print(classification_report(y_val, y_pred))参数说明class_weightbalanced自动按类别频率反比加权early_stopping(50)表示验证集损失 50 轮不下降就停num_leaves31是默认值样本量小可以降到 15 防止过拟合。4.2 一维卷积网络怎么搭适合端到端时序分类如果不想手工做特征可以试试一维卷积。输入是原始窗口(window_size, n_features)用几层 Conv1D 池化最后接全连接分类。import torch import torch.nn as nn class DrivingCNN(nn.Module): def __init__(self, n_features, n_classes): super().__init__() self.conv1 nn.Conv1d(n_features, 64, kernel_size5, padding2) self.conv2 nn.Conv1d(64, 128, kernel_size3, padding1) self.pool nn.AdaptiveAvgPool1d(1) self.fc nn.Linear(128, n_classes) self.relu nn.ReLU() self.dropout nn.Dropout(0.3) def forward(self, x): # x: (batch, window_size, n_features) - (batch, n_features, window_size) x x.permute(0, 2, 1) x self.relu(self.conv1(x)) x self.relu(self.conv2(x)) x self.pool(x).squeeze(-1) x self.dropout(x) return self.fc(x)逻辑说明permute把时间维换到后面因为 PyTorch 的 Conv1d 要求通道在前。AdaptiveAvgPool1d(1)把时间维压成 1相当于全局平均池化。训练时用交叉熵损失优化器选 Adam学习率 1e-3。4.3 模型融合投票与堆叠的取舍比赛里单模型分数不够时常见做法是 LightGBM CNN 随机森林做软投票。软投票取概率平均比硬投票稳。堆叠stacking用验证集预测结果训练第二层但要注意别过拟合。from sklearn.ensemble import VotingClassifier, RandomForestClassifier rf RandomForestClassifier(n_estimators300, class_weightbalanced, random_state42) voting VotingClassifier( estimators[(lgb, clf), (rf, rf)], votingsoft ) voting.fit(X_train, y_train) print(f融合模型验证集准确率: {voting.score(X_val, y_val):.4f})注意VotingClassifier 里的clf如果已经 early stopping 训练过直接放进去会重新训练。最好用clone或者重新实例化。5. 避坑与排查那些让分数一夜回到解放前的细节5.1 滑窗边界标签取多数导致急加速样本被吞现象急加速类召回率极低混淆矩阵里大量急加速被预测成正常行驶。 原因滑窗取多数标签时急加速段通常只占窗口一小部分多数投票直接把它投没了。 解决改用窗口中心点标签或者对少数类窗口做保留判断——只要窗口内少数类占比超过 30%就强制标为少数类。5.2 标准化在划分数据集之前做导致信息泄露现象线下验证集准确率 98%线上测试只有 80%。 原因StandardScaler在全部数据上fit验证集的均值和方差信息泄露到了训练过程。 解决先train_test_split再在训练集上fit_transform验证集只transform。这个坑在比赛总结里被反复提到但每年都有人踩。5.3 频域特征在窗口长度不是 2 的幂时频率轴对不齐现象不同窗口的 FFT 频率轴长度不一致拼接特征时报错。 原因np.fft.rfft的输出长度是window_size // 2 1如果窗口长度不固定频率轴长度就不同。 解决固定窗口长度或者在 FFT 前做零填充到最近的 2 的幂。我一般直接固定窗口为 128 或 256 个点。5.4 车速字段缺失或异常值未处理直接进模型现象模型对急减速的区分度很差特征重要性里speed排最后。 原因车速字段有大量 0 值或跳变可能是传感器丢包直接填 0 会让模型学到错误模式。 解决先做缺失值插值线性或前向填充再对跳变做中值滤波。车速的差分加速度比原始车速更有用。5.5 随机种子没固定导致结果不可复现现象每次跑出来的准确率差 2 到 3 个百分点比赛提交时不知道选哪次。 原因LightGBM、PyTorch、sklearn 的随机种子没全部固定。 解决在代码开头统一设置random.seed、np.random.seed、torch.manual_seedLightGBM 的random_state也要设。别小看这个比赛总结里有人因为没固定种子最后提交了分数最低的那次。6. 从比赛到落地驾驶行为分析还能怎么用比赛结束后这套链路其实可以迁移到很多实际场景。比如车队管理里做急刹车统计保险行业做驾驶风险评分或者车机系统里做实时驾驶风格提醒。区别在于比赛数据是离线批处理落地场景往往要求实时推理。实时推理的第一个问题是窗口怎么维护。离线时你可以拿到整段数据再滑窗实时时只能用环形缓冲区保存最近 N 个点。我一般会用一个collections.deque(maxlenwindow_size)来存传感器数据每来一个新点就更新一次特征但不用每个点都推理可以每 0.5 秒推理一次。from collections import deque import numpy as np class RealTimePredictor: def __init__(self, model, scaler, window_size150, n_features7): self.buffer deque(maxlenwindow_size) self.model model self.scaler scaler self.window_size window_size self.n_features n_features def update(self, sample): sample: 长度为 n_features 的一维数组 self.buffer.append(sample) if len(self.buffer) self.window_size: return None window np.array(self.buffer) # (window_size, n_features) feats extract_time_features(window) feats np.hstack([feats, extract_freq_features(window)]) feats self.scaler.transform(feats.reshape(1, -1)) pred self.model.predict(feats)[0] return pred逻辑说明deque自动丢弃最旧的点保持窗口长度固定。update每调用一次就返回一个预测或 None。参数说明window_size要和训练时一致n_features是传感器通道数。注意extract_time_features和extract_freq_features必须和训练时完全一致否则 scaler 会报维度错误。验证方法上我习惯用混淆矩阵加每类 F1 来评估而不是只看准确率。驾驶行为分析里急转弯的 F1 往往最低因为样本少且容易和正常变道混淆。如果急转弯 F1 低于 0.6就要回头检查角速度特征是否被加速度特征淹没了。还有一个技巧是给模型加一个“不确定”类别。当预测概率最大值低于 0.5 时输出“不确定”让上层系统决定是忽略还是请求更多数据。这在落地时比强行分类更安全。最后说个血泪教训别在特征工程上炫技。我见过有人把几百维特征全丢进去结果 LightGBM 训练了半小时分数还不如 50 维手工特征。特征重要性排前 20 的往往就是均值、方差、峰峰值、低频能量占比这几个。先把这几个调好再考虑加新特征。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询