简介:这份资源是第七届泰迪杯数据挖掘比赛的车辆驾驶行为分析完整项目,面向计算机、人工智能、自动化等专业的学生与教师,也适合作为课程设计、毕业设计或数据挖掘入门练手案例。项目在传统驾驶行为评测基础上,结合省、市、县级地区的温度、天气、湿度等环境特征,建立环境评测模型,探究环境因素对车辆行驶节能的影响,并将环境因素融入驾驶行为评测模型,形成较完整的赛题解决方案。压缩包共10个文件,约1.82MB,包含5个ipynb实验笔记、2个Python脚本、2个Markdown说明文档和1份比赛总结docx,覆盖数据预处理、异常值处理、经纬度与路程速度分析、KMeans聚类及汽车评价体系得分等模块。目前已有167人学习下载。读者可据此复现完整建模流程,理解环境因素与驾驶行为的融合思路,并参考比赛总结梳理赛题要点与改进方向,代码经过测试可直接运行,也支持在此基础上修改扩展。
1. 从泰迪杯赛题到可复现工程:车辆驾驶行为分析到底在做什么
第一次拿到「车辆驾驶行为分析」这个赛题方向时,很多人会下意识往「识别急加速、急刹车」上想,觉得无非是给几个阈值。但真正跑过一遍泰迪杯数据挖掘类赛题的人都知道,难点从来不在定义,而在数据本身——车载终端采回来的往往是高频、多字段、带大量缺失和漂移的时间序列,字段之间还互相耦合。这个方向要解决的核心问题是:把连续的车辆运动信号,切分成一段段有语义的驾驶行为片段,再对片段做分类、统计和风险刻画。它适合已经会一点 Python、想找一个完整数据挖掘闭环练手的人,也适合做车联网、保险风控、车队管理的从业者拿来对照自己的业务口径。整套流程通常包含数据清洗、特征构造、行为切分、模型分类、结果可视化五块,源码和文档说明的价值就在于把这五块串成一条能跑通的链路,而不是停在 notebook 里。
2. 数据挖掘全流程拆解:从原始车载信号到行为标签
2.1 先搞清楚数据长什么样再动手
车辆驾驶行为分析的数据来源,常见的是 OBD 或车载 T-Box 采集的时序表,字段一般包括时间戳、车速、发动机转速、节气门开度、经纬度、三轴加速度等。泰迪杯这类赛题给的通常是脱敏后的 CSV 或 Excel,一个文件对应一辆车一段时间的行程。动手前必须先做三件事:看字段类型、看采样频率、看缺失分布。采样频率决定了后面滑窗的窗口长度,缺失分布决定了你是插值还是直接丢段。
import pandas as pd import numpy as np # 读取单辆车的行程数据,parse_dates 把时间列转成真正的时间类型 df = pd.read_csv("trip_001.csv", parse_dates=["timestamp"]) df = df.sort_values("timestamp").reset_index(drop=True) # 采样间隔统计:看是不是均匀采样,这决定滑窗能不能按固定行数切 dt = df["timestamp"].diff().dt.total_seconds().dropna() print("采样间隔中位数(秒):", dt.median()) print("采样间隔标准差:", dt.std()) # 缺失情况:每一列的缺失比例,超过 30% 的列要慎重使用 missing_ratio = df.isnull().mean().sort_values(ascending=False) print(missing_ratio[missing_ratio > 0])这段代码的逻辑是先建立时间顺序,再判断采样是否均匀。参数上,dt.median()是后续滑窗长度的基准,如果中位数是 1 秒,窗口取 5 到 10 秒比较合理;如果标准差远大于中位数,说明采样不均匀,就不能按固定行数切窗,得改成按时间重采样。缺失比例超过 30% 的列,比如某些车的加速度字段,直接拿来算特征会引入大量噪声,稳妥做法是丢弃或只用它做辅助校验。
2.2 特征构造:把物理量翻译成行为语言
原始字段本身不直接表达行为,需要构造派生特征。车速的一阶差分是加速度,加速度的正负和幅值对应急加速、急减速;车速与转速的比值能反映挡位和负载;方向角变化率对应急转弯。这一步是整个数据挖掘流程里最吃经验的地方,特征选得好,后面用逻辑回归都能出结果,选得差,上梯度提升也救不回来。
# 按时间排序后计算派生特征 df["acc"] = df["speed"].diff() / dt.reindex(df.index).fillna(1) # 加速度 df["jerk"] = df["acc"].diff() # 加速度变化率 df["speed_rpm_ratio"] = df["speed"] / (df["rpm"] + 1e-6) # 车速转速比 # 方向角变化率,需要先把经纬度转成方位角 def bearing(lat1, lon1, lat2, lon2): lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2]) dlon = lon2 - lon1 x = np.sin(dlon) * np.cos(lat2) y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(dlon) return np.degrees(np.arctan2(x, y)) df["bearing"] = bearing(df["lat"].shift(), df["lon"].shift(), df["lat"], df["lon"]) df["bearing_rate"] = df["bearing"].diff().abs()acc用差分除以时间间隔得到,注意这里对dt做了reindex和填充,避免首行 NaN 传播。jerk是加速度的变化率,急加速和急减速的切换往往伴随 jerk 尖峰,是区分「平稳驾驶」和「激进驾驶」的关键特征。bearing_rate用方位角差分绝对值表示,值突然变大说明车辆在急转。参数上,1e-6是防止转速为 0 时除零,这类小保护在工程里很常见,别嫌麻烦。
2.3 行为切分:滑窗还是事件触发
有了特征,下一步是把连续序列切成行为片段。两种主流做法:固定滑窗和事件触发。固定滑窗实现简单,适合做统计类特征;事件触发更贴近真实行为,但需要先定义触发条件。泰迪杯赛题里我一般先用滑窗做基线,再用阈值触发做精修。
# 固定滑窗:窗口 10 秒,步长 5 秒,按时间而非行数切 window_sec, step_sec = 10, 5 df = df.set_index("timestamp") windows = [] start = df.index.min() while start + pd.Timedelta(seconds=window_sec) <= df.index.max(): seg = df.loc[start:start + pd.Timedelta(seconds=window_sec)] if len(seg) >= 3: # 至少 3 个点才算有效窗口 windows.append(seg) start += pd.Timedelta(seconds=step_sec) print("有效窗口数:", len(windows))按时间切窗而不是按行数,是为了兼容采样不均匀的情况。window_sec取 10 秒是经验值,太短特征不稳,太长会把多种行为混在一起。len(seg) >= 3这个下限是防止空窗或单点窗污染统计。切完之后,每个窗口算一组聚合特征:加速度均值、最大值、jerk 的 95 分位、方向角变化率均值等,这些就是喂给分类器的输入。
2.4 分类模型与标签口径
行为标签通常分几类:正常行驶、急加速、急减速、急转弯、怠速。如果赛题没给标签,就得用规则先打弱标签,再训练模型。规则打标时阈值要结合数据分布定,不能照搬教科书。比如急减速,常见口径是加速度小于 -3 m/s²,但城市工况和高速工况差别很大,稳妥做法是按分位数取,比如取加速度最低的 5% 作为急减速候选。
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # X 是窗口聚合特征,y 是行为标签 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42) clf = RandomForestClassifier(n_estimators=200, max_depth=8, class_weight="balanced", random_state=42) clf.fit(X_train, y_train) print(classification_report(y_test, clf.predict(X_test)))class_weight="balanced"很关键,行为类别天然不均衡,正常行驶占绝大多数,不加这个参数模型会倾向于全预测正常。max_depth=8是防止过拟合的保守设置,特征维度不高时够用。评估时别只看准确率,要看每一类的召回,尤其是急加速、急减速这种少数类,召回低说明阈值或特征有问题。
3. 源码工程怎么读:目录结构与模块职责
3.1 一个能跑通的工程该有哪些文件
拿到「python项目源码+文档说明」这类交付物,别急着从头读代码,先看目录结构。一个规范的车辆驾驶行为分析工程,通常包含数据目录、预处理模块、特征模块、模型模块、可视化模块和入口脚本。文档说明一般会写清楚运行顺序和依赖版本。下面是一个常见的组织方式,具体文件名各项目不同,但职责划分大同小异。
| 目录/文件 | 职责 | 阅读优先级 |
|---|---|---|
| data/ | 原始与中间数据 | 低,先看格式 |
| preprocess.py | 清洗、重采样、缺失处理 | 高 |
| features.py | 派生特征与窗口聚合 | 高 |
| model.py | 训练、评估、保存 | 中 |
| visualize.py | 行为分布与轨迹图 | 低 |
| main.py | 串联全流程入口 | 最高 |
先读main.py,把调用链理清楚,再回头逐个看模块。这样读比按文件顺序啃快得多,也不容易迷路。
3.2 环境依赖与运行顺序
Python 环境建议用 3.8 到 3.10,太新的版本有些老依赖会装不上。依赖一般集中在requirements.txt,核心是 pandas、numpy、scikit-learn、matplotlib,做轨迹图可能还会用到 folium 或 plotly。
# 创建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt # 按入口脚本运行,通常需要先改配置里的数据路径 python main.py --data_dir ./data --out_dir ./output运行顺序上,预处理必须在特征之前,特征必须在训练之前,这个链条不能乱。如果main.py里用了命令行参数,先看--help输出,确认数据路径和输出路径怎么传。很多跑不通的情况不是代码错,而是路径写死成了作者本机的绝对路径,改成相对路径就好。
4. 避坑与排查:那些让结果翻车的细节
4.1 时间戳没排序导致特征全错
现象:加速度特征出现大量异常值,模型准确率忽高忽低。原因:原始数据按采集顺序存储,但中间可能有乱序或重复时间戳,差分计算直接错位。解决:读入后立刻sort_values("timestamp")并drop_duplicates("timestamp"),再做任何差分。这个坑我踩过不止一次,排序这行代码看着不起眼,少了它后面全白干。
4.2 采样不均匀却按行数切窗
现象:窗口内点数差异很大,有的窗口 3 个点,有的 30 个点,聚合特征方差爆炸。原因:车载终端在信号弱时会丢点,采样间隔不固定。解决:改成按时间切窗,或者先重采样到固定频率再切。判断方法就是前面那段采样间隔统计,标准差大就说明不均匀。
4.3 阈值照搬教科书导致标签失真
现象:急减速样本极少,模型学不到。原因:教科书阈值 -3 m/s² 在低速工况下几乎不会触发。解决:按数据分位数定阈值,或者分速度区间设不同阈值。低速段用更小的绝对值,高速段用更大的,这样标签分布才合理。
4.4 类别不均衡没处理
现象:模型对正常行驶召回 99%,对急加速召回不到 10%。原因:没设class_weight,也没做重采样。解决:优先用class_weight="balanced",效果不够再考虑 SMOTE 过采样,但要注意过采样后必须用独立验证集评估,否则指标虚高。
4.5 经纬度直接当特征喂模型
现象:模型在训练集表现好,换一辆车就崩。原因:经纬度是绝对位置,不同行程范围不同,模型学到的是路线而不是行为。解决:经纬度只用来算方向角变化率和位移,不要直接把原始经纬度作为特征。这个坑很隐蔽,因为训练集上不会暴露。
5. 进阶技巧:用分位数阈值和交叉验证把结果做扎实
规则打标最容易被质疑的就是阈值主观。我一般会用分位数法把阈值和数据分布绑定,再用分组交叉验证检验泛化。具体做法是:对加速度、jerk、方向角变化率分别取 1% 和 5% 分位作为候选阈值,生成多组标签,分别训练模型,看哪组标签下模型在留出车辆上的表现最稳。留出车辆的意思是,训练集和验证集按车辆 ID 划分,而不是随机划分,这样能真实反映换车后的效果。
from sklearn.model_selection import GroupKFold import numpy as np # 按车辆分组做交叉验证,避免同一辆车的窗口同时出现在训练和验证集 groups = df_windows["vehicle_id"].values gkf = GroupKFold(n_splits=5) scores = [] for train_idx, val_idx in gkf.split(X, y, groups): clf.fit(X[train_idx], y[train_idx]) scores.append(clf.score(X[val_idx], y[val_idx])) print("分组交叉验证准确率:", np.mean(scores), "±", np.std(scores))GroupKFold的groups参数传车辆 ID,保证同一辆车的窗口不跨折。这个设置比随机划分严格得多,指标通常会掉几个点,但掉下来的才是真实水平。如果分组交叉验证和随机划分差距很大,说明模型过拟合到了车辆个体特征,得回头检查特征里有没有混入车辆相关的信息。
另一个实用技巧是把行为统计结果做成可解释的报表,而不是只给一个分类标签。比如每辆车输出急加速次数、急减速次数、急转弯次数、百公里急刹频次,这些指标业务方看得懂,也方便横向对比。可视化上,轨迹图叠加行为标记点比单纯的热力图更直观,但要注意轨迹点太多时渲染会卡,按行为类型分层绘制能缓解。
最后说个习惯:每次改完特征或阈值,我都会把当次的配置和指标记到一个简单的日志文件里,包括数据版本、特征列表、阈值、交叉验证结果。这个习惯救过我很多次,因为调参调到最后经常会忘记哪一版效果最好,有了日志就能回溯。车辆驾驶行为分析这类任务,数据口径和特征定义比模型选择重要得多,把这两块做扎实,剩下的都是水到渠成。希望帮到你。
本文还有配套的精品资源,点击获取