☰
车辆驾驶行为分析:从三轴传感器到驾驶风格标签的完整链路
2026/10/9 22:14:00 网站建设 项目流程

简介:这份资源是第七届泰迪杯数据挖掘比赛的车辆驾驶行为分析完整参赛项目,面向计算机、人工智能、通信工程等专业的在校学生及数据挖掘初学者,可用于课程设计、毕业设计或竞赛复盘。项目在传统驾驶行为评测基础上,结合省、市、县级地区的温度、天气、湿度等环境特征,建立环境评测模型,探究环境因素对车辆行驶节能的影响,并将环境因素融入驾驶行为评测体系,涉及数据预处理、异常值处理、聚类与评价体系构建等环节。压缩包共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:三轴角速度,单位常见为 °/s
  • speed:车速,单位 km/h
  • timestamp:时间戳,可能是毫秒级或秒级

采样频率是第一个要确认的参数。常见做法是 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), sharex=True) axes[0].plot(df['timestamp'], df['acc_x'], label='acc_x') axes[0].plot(df['timestamp'], df['acc_y'], label='acc_y') axes[0].plot(df['timestamp'], df['acc_z'], label='acc_z') axes[0].legend(); axes[0].set_ylabel('Acceleration (g)') axes[1].plot(df['timestamp'], df['gyro_z'], color='r', label='gyro_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_size=150, step=75) 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_counts=True) for u, c in zip(unique, counts): print(f'类别 {u}: {c} 样本, 占比 {c/len(y)*100:.2f}%')

常见做法是先用class_weight='balanced'让树模型自动加权,或者在滑窗时对少数类做过采样。注意不要直接在原始时序上做 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, fs=50): """ 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), d=1/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.01,RMS 可能是 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_size=0.2, random_state=42, stratify=y ) clf = lgb.LGBMClassifier( n_estimators=500, learning_rate=0.05, num_leaves=31, max_depth=-1, class_weight='balanced', random_state=42 ) clf.fit(X_train, y_train, eval_set=[(X_val, y_val)], eval_metric='multi_logloss', callbacks=[lgb.early_stopping(50)]) y_pred = clf.predict(X_val) print(classification_report(y_val, y_pred))

参数说明:class_weight='balanced'自动按类别频率反比加权;early_stopping(50)表示验证集损失 50 轮不下降就停;num_leaves=31是默认值,样本量小可以降到 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_size=5, padding=2) self.conv2 = nn.Conv1d(64, 128, kernel_size=3, padding=1) 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_estimators=300, class_weight='balanced', random_state=42) voting = VotingClassifier( estimators=[('lgb', clf), ('rf', rf)], voting='soft' ) 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_seed,LightGBM 的random_state也要设。别小看这个,比赛总结里有人因为没固定种子,最后提交了分数最低的那次。

6. 从比赛到落地:驾驶行为分析还能怎么用

比赛结束后,这套链路其实可以迁移到很多实际场景。比如车队管理里做急刹车统计,保险行业做驾驶风险评分,或者车机系统里做实时驾驶风格提醒。区别在于,比赛数据是离线批处理,落地场景往往要求实时推理。

实时推理的第一个问题是窗口怎么维护。离线时你可以拿到整段数据再滑窗,实时时只能用环形缓冲区保存最近 N 个点。我一般会用一个collections.deque(maxlen=window_size)来存传感器数据,每来一个新点就更新一次特征,但不用每个点都推理,可以每 0.5 秒推理一次。

from collections import deque import numpy as np class RealTimePredictor: def __init__(self, model, scaler, window_size=150, n_features=7): self.buffer = deque(maxlen=window_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 的往往就是均值、方差、峰峰值、低频能量占比这几个。先把这几个调好,再考虑加新特征。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询