1. 项目概述:从数据到决策的桥梁
拿到“河北省第三届研究生数学建模B题(二等)交通检测器数据质量控制及预测”这个题目,很多同学的第一反应可能是:这不就是一道数据处理加预测的题吗?但如果你真这么想,可能就错过了这道题最核心的价值。这道题本质上是在考察我们如何将一个现实世界中“脏乱差”的原始数据流,通过一系列严谨的工程化与模型化处理,最终转化为能够支撑交通管理决策的可靠信息。它模拟的正是智慧交通系统后台每天都在发生的真实工作流。我当年做这道题时,最大的感触是,模型算法固然重要,但前期对数据“脾气秉性”的深刻理解与“外科手术”般的精准处理,往往决定了你最终预测结果的上限。这道题非常适合那些希望从“调包侠”进阶为“问题解决者”的同学,它能让你完整地体验一个数据科学项目的全生命周期。
简单来说,题目给了我们交通检测器采集到的一堆原始数据,比如车流量、速度、占有率等。这些数据来自现实世界的传感器,不可避免地会存在各种问题:有些数据点干脆缺失了,像断线的珠子;有些数据明显不合理,比如在高速公路上出现时速300公里的“幽灵车”;还有些数据看起来正常,但细究之下存在隐蔽的异常或噪声。我们的任务分两步走:第一步,数据质量控制,就是当好数据的“质检员”和“修复师”,把有问题的数据找出来、修好或者合理地处理掉,得到一份干净、可信的数据集。第二步,交通流预测,就是基于这份干净的历史数据,扮演“交通先知”,预测未来短时间内的交通状态(比如接下来5分钟、15分钟的车流量或速度)。这两个环节环环相扣,质量控制是预测的基石,预测的准确性反过来也验证了质量控制的有效性。
2. 核心思路与方案选型:为什么是“清洗+预测”的组合拳?
面对这道题,一个清晰的顶层设计思路至关重要。我们不能一上来就埋头搞复杂的预测模型,那样很容易在“垃圾数据”上训练出“垃圾模型”。整个解题框架应该遵循“数据理解 -> 质量评估 -> 清洗修复 -> 特征工程 -> 模型预测 -> 结果分析”的闭环。这里重点聊聊几个关键环节的选型考量。
2.1 数据质量控制:从“粗筛”到“精修”的递进策略
数据质量控制不是简单地把“看起来不对劲”的数据删掉。粗暴的删除会损失信息,甚至引入偏差。一个更专业的思路是采用多层次、递进式的清洗策略。
首先,基于规则的粗筛。这是第一道防线,主要针对那些明显违背物理规律或统计常识的“硬伤”数据。例如:
- 物理阈值法:城市道路车速超过150km/h?这几乎不可能,可以直接标记为异常。这个阈值需要根据道路等级(高速、快速路、主干道)来动态设定。
- 逻辑一致性检查:交通流理论中,流量、速度、占有率三个参数之间存在内在关系(例如格林希尔治模型)。当占有率很高(接近拥堵)时,速度必然很低。如果出现“高占有率+高速度”的组合,数据很可能有问题。
- 突变点检测:相邻时间片的数据(如前后5分钟)通常具有连续性。如果车流量从100辆/5分钟瞬间跳到1000辆/5分钟,又立刻跌回,这很可能是传感器瞬时故障或通信干扰,需要重点审查。
注意:基于规则的筛选,其阈值设定需要结合具体路段的历史数据和常识,不能拍脑袋决定。最好能对历史数据做一个分布分析(如箱线图),用统计方法(如3σ原则)辅助确定动态阈值。
其次,基于模型的精修与插补。对于缺失值和被标记为“可疑”但非“绝对错误”的数据,直接删除并非上策。这时需要用到数据修复技术。
- 时间序列插补:交通数据是典型的时间序列,具有趋势性、周期性和相关性。对于短时缺失(如连续缺失几个时间点),可以采用线性插值、样条插值。对于较长时段的缺失,更优的方法是使用时间序列预测模型,如ARIMA(自回归积分滑动平均模型)或更简单的移动平均,利用前后时段的数据特征来预测并填充缺失值。这比直接用均值或中位数填充更能保留数据的时间模式。
- 空间相关性利用:如果题目数据提供了相邻检测器的信息(这是常见设置),那么空间相关性就是强大的修复工具。相邻路段的交通状态通常高度相关。一个检测器数据异常,可以用其上下游检测器同时刻的数据进行加权修正或回归估算,这在实际工程中非常有效。
2.2 预测模型选型:没有最好,只有最合适
完成数据清洗后,我们得到了一份相对干净的时间序列数据。接下来要预测未来时段的交通参数(如流量)。模型选择是整个项目的亮点所在,也是拉开差距的地方。当时我们团队对比了几种主流方案:
- 经典时间序列模型(ARIMA/SARIMA):这是很多人的第一选择。它的优势在于理论成熟,对具有明显趋势和季节性的序列表现稳定。对于交通流,我们通常能观察到早高峰、晚高峰的日周期,以及周周期(工作日 vs 周末)。SARIMA(季节性ARIMA)可以很好地捕捉这些固定周期。但是,它的缺点也很明显:模型参数(p,d,q)的确定需要一定经验,且它本质上是线性模型,对交通流中常见的非线性、突发性拥堵(如事故导致的)捕捉能力有限。
- 机器学习回归模型:我们可以将预测问题转化为监督学习问题。例如,用过去N个时间片的数据(流量、速度、占有率)作为特征,来预测未来第M个时间片的目标值。可以尝试支持向量回归(SVR)、随机森林(RF)、梯度提升树(XGBoost/LightGBM)。这些模型能自动学习非线性关系,对特征工程的要求更高。我们需要构建有效的特征,比如滞后特征(前1期、2期…N期的值)、滑动统计特征(过去1小时的均值、方差)、时间特征(小时、星期几、是否节假日)等。树模型对异常值相对鲁棒,这在处理过清洗但可能仍有残余噪声的数据时是个优点。
- 深度学习模型(LSTM/GRU):这是当时我们认为最有潜力也最复杂的方案。长短时记忆网络(LSTM)是专门为序列数据设计的,它能自动学习长期依赖关系,非常适合交通流预测。你可以把连续多个时间片的数据(比如过去2小时的数据,按5分钟一个点,就是24个时间步)作为一个序列输入LSTM,让它去学习序列内部的演变模式,然后输出未来几个时间点的预测值。实操心得:LSTM虽然强大,但需要大量的数据、仔细的调参(层数、神经元数、dropout率)和较长的训练时间。在数学建模竞赛有限的时间内,如果数据量不是特别大,使用LSTM可能会面临过拟合或调参不当导致效果反而不如简单模型的风险。但它绝对是论文中的一个重要加分项,体现你对前沿技术的掌握。
我们最终的方案是组合模型:先用SARIMA捕捉数据的线性趋势和固定周期,然后用XGBoost去拟合SARIMA的残差(即模型未能解释的非线性、突发性部分)。这种“传统模型+机器学习”的思路,在实践中往往能取得比单一模型更稳健的效果。
3. 数据质量控制实战:手把手处理一份“带病”数据
假设我们拿到了一份来自某城市快速路的5分钟粒度检测数据,包含时间戳、检测器ID、流量(辆/5分钟)、平均速度(km/h)、占有率(%)等字段。数据跨度一个月。下面我们一步步拆解清洗过程。
3.1 数据探索与异常诊断
首先,绝不是直接跑模型。用Python的Pandas和Matplotlib/Seaborn进行探索性数据分析(EDA)是必不可少的。
import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 1. 加载数据,解析时间戳 df = pd.read_csv('traffic_detector_data.csv') df['timestamp'] = pd.to_datetime(df['timestamp']) df.set_index('timestamp', inplace=True) # 2. 查看数据概览:缺失值、基本统计 print(df.info()) print(df.describe()) # 3. 单一检测器数据可视化(以某个关键检测器为例) detector_id = 'D001' df_detector = df[df['detector_id'] == detector_id].copy() fig, axes = plt.subplots(3, 1, figsize=(15, 10)) axes[0].plot(df_detector.index, df_detector['flow'], label='Flow') axes[0].set_ylabel('Flow (veh/5min)') axes[0].legend() axes[0].set_title(f'Traffic Flow for {detector_id}') axes[1].plot(df_detector.index, df_detector['speed'], label='Speed', color='orange') axes[1].set_ylabel('Speed (km/h)') axes[1].legend() axes[2].plot(df_detector.index, df_detector['occupancy'], label='Occupancy', color='green') axes[2].set_ylabel('Occupancy (%)') axes[2].legend() plt.tight_layout() plt.show()通过描述性统计和可视化,我们可能立刻发现一些问题:速度列存在最大值999(显然是无效填充值),流量在某些时段为0或负数,图形上存在明显的“毛刺”和连续缺失段。
3.2 实施多层次清洗规则
基于发现的问题,我们编写清洗函数。
def clean_traffic_data(df, detector_list=None): """ 对交通检测器数据进行清洗。 df: 输入的DataFrame detector_list: 指定需要处理的检测器列表,默认为None(处理所有) """ if detector_list: df_clean = df[df['detector_id'].isin(detector_list)].copy() else: df_clean = df.copy() # 规则1:处理明显无效的填充值(如速度=999) df_clean['speed'] = df_clean['speed'].replace(999, np.nan) df_clean['flow'] = df_clean['flow'].replace([-1, -999], np.nan) # 处理负流量 # 规则2:基于物理逻辑的异常过滤 # 假设城市快速路合理速度范围 [5, 120] km/h speed_lower, speed_upper = 5, 120 df_clean.loc[~df_clean['speed'].between(speed_lower, speed_upper), 'speed'] = np.nan # 流量非负,且设置一个合理的上限(根据车道数估算,如单车道5分钟最大通过约100辆?需调研) flow_upper = 200 # 示例值,需根据实际情况调整 df_clean.loc[(df_clean['flow'] < 0) | (df_clean['flow'] > flow_upper), 'flow'] = np.nan # 占有率范围 [0, 100]% df_clean.loc[~df_clean['occupancy'].between(0, 100), 'occupancy'] = np.nan # 规则3:基于交通流基本关系的过滤(格林希尔治线性模型简化版) # 当占有率极高时(如>30%),速度不可能很高。这里用一个简单规则: high_occ_threshold = 30 high_speed_threshold = 60 conflict_mask = (df_clean['occupancy'] > high_occ_threshold) & (df_clean['speed'] > high_speed_threshold) # 将冲突的数据标记为NaN(通常速度更易受干扰,选择置空速度) df_clean.loc[conflict_mask, 'speed'] = np.nan # 也可以选择同时置空流量和速度,取决于后续插补策略 return df_clean df_cleaned = clean_traffic_data(df, detector_list=['D001'])3.3 高级修复:时间序列与空间插补
经过规则清洗,我们得到了一个包含许多NaN值的数据集。下一步是智能填充。
from sklearn.impute import KNNImputer # 注意:使用KNN插补需要将数据转换为数值矩阵,且可能忽略时间顺序。对于时间序列,优先考虑时间方法。 # 方法A:时间序列插补(以流量为例) def temporal_impute(series, method='linear'): """ 对时间序列进行插补。 method: 'linear', 'time' (基于时间索引的线性), 'spline', 'ffill' (前向填充)等 """ if method in ['linear', 'time', 'spline']: # 使用pandas的interpolate方法 return series.interpolate(method=method, limit_direction='both') elif method == 'ffill': return series.ffill().bfill() # 前后填充结合 else: return series # 对清洗后的每个检测器、每个参数进行时间插补 df_filled = df_cleaned.groupby('detector_id').apply(lambda group: group.apply(temporal_impute, method='time')) # 方法B:如果数据包含空间信息(如上下游检测器),可以考虑空间插补 # 假设我们有检测器D001, D002, D003的空间顺序数据 # 可以构建一个简单的线性回归:D001_t = a * D002_t + b * D003_t + c,用正常时刻的数据训练,预测缺失时刻的值。 # 这里省略具体代码,思路是利用sklearn的LinearRegression。 print(f"清洗前缺失值数量: {df.isnull().sum().sum()}") print(f"规则清洗后缺失值数量: {df_cleaned.isnull().sum().sum()}") print(f"时间插补后缺失值数量: {df_filled.isnull().sum().sum()}")经过这一套组合拳,我们得到的数据质量已经大幅提升,为后续的预测建模打下了坚实的基础。
4. 交通流预测模型构建与实现
有了干净的数据,我们就可以着手构建预测模型。我们以预测未来15分钟(即未来3个5分钟时段)的交通流量为例。
4.1 特征工程:从原始数据中提炼信息
特征决定了模型性能的天花板。对于时间序列预测,特征主要来自以下几个方面:
- 滞后特征:过去N个时间点的值。这是最重要的特征。例如,用过去1小时(12个点)的流量、速度、占有率来预测未来。
- 滑动窗口统计特征:过去一段时间窗口内的统计量,如均值、标准差、最大值、最小值。这能帮助模型感知近期趋势和波动。
- 时间特征:提取时间戳中的信息,如
小时、一天中的分钟数、星期几、是否周末、是否节假日。这对于捕捉交通流的周期性至关重要。 - 交互特征:例如
流量/速度(可以反映密度)、过去一段时间内流量的变化率等。
def create_features(df, target_col='flow', lags=12, forecast_horizon=3): """ 为时间序列预测创建特征。 df: 单个检测器的时序DataFrame (index为datetime) target_col: 要预测的目标列名 lags: 使用的滞后阶数(历史多少个时间点) forecast_horizon: 预测未来多少个时间点 """ df_feat = df.copy() # 1. 创建滞后特征 for lag in range(1, lags+1): df_feat[f'{target_col}_lag_{lag}'] = df_feat[target_col].shift(lag) # 也可以为速度、占有率创建滞后特征 # df_feat[f'speed_lag_{lag}'] = df_feat['speed'].shift(lag) # 2. 创建滑动窗口统计特征 (窗口大小=6,即过去半小时) window_size = 6 df_feat[f'{target_col}_rolling_mean_6'] = df_feat[target_col].rolling(window=window_size, min_periods=1).mean().shift(1) df_feat[f'{target_col}_rolling_std_6'] = df_feat[target_col].rolling(window=window_size, min_periods=1).std().shift(1) # 3. 创建时间特征 df_feat['hour'] = df_feat.index.hour df_feat['day_of_week'] = df_feat.index.dayofweek # Monday=0, Sunday=6 df_feat['is_weekend'] = (df_feat['day_of_week'] >= 5).astype(int) # 更精细的周期编码:使用正弦余弦变换 df_feat['hour_sin'] = np.sin(2 * np.pi * df_feat['hour']/24) df_feat['hour_cos'] = np.cos(2 * np.pi * df_feat['hour']/24) # 4. 创建目标值(未来第forecast_horizon个点的值) df_feat['target'] = df_feat[target_col].shift(-forecast_horizon) # 删除因创建滞后特征和未来目标值而产生的NaN行 df_feat.dropna(inplace=True) return df_feat # 应用特征工程 df_detector_feat = create_features(df_filled.loc[df_filled['detector_id']=='D001', ['flow']], target_col='flow', lags=12, forecast_horizon=3)4.2 模型训练与评估:以XGBoost为例
我们将数据集按时间顺序划分为训练集和测试集(严禁随机划分!),并使用XGBoost进行训练。
import xgboost as xgb from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 准备数据 X = df_detector_feat.drop(columns=['target']) y = df_detector_feat['target'] # 按时间顺序划分:前80%训练,后20%测试 split_idx = int(len(X) * 0.8) X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:] # 创建并训练XGBoost模型 model = xgb.XGBRegressor( n_estimators=200, learning_rate=0.05, max_depth=6, subsample=0.8, colsample_bytree=0.8, random_state=42, n_jobs=-1 ) model.fit(X_train, y_train) # 预测与评估 y_pred = model.predict(X_test) mae = mean_absolute_error(y_test, y_pred) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) r2 = r2_score(y_test, y_pred) print(f"测试集评估结果:") print(f" MAE (平均绝对误差): {mae:.2f} 辆/5分钟") print(f" RMSE (均方根误差): {rmse:.2f} 辆/5分钟") print(f" R² Score: {r2:.4f}") # 可视化预测结果对比 plt.figure(figsize=(15,5)) plt.plot(y_test.index, y_test.values, label='Actual Flow', alpha=0.7) plt.plot(y_test.index, y_pred, label='Predicted Flow', alpha=0.7, linestyle='--') plt.xlabel('Time') plt.ylabel('Flow (veh/5min)') plt.title(f'Traffic Flow Prediction - XGBoost (MAE={mae:.1f}, R²={r2:.3f})') plt.legend() plt.grid(True, alpha=0.3) plt.show()4.3 模型融合思路:SARIMA + XGBoost
为了追求更高的精度和鲁棒性,我们可以尝试融合模型。基本思想是:用SARIMA捕捉线性趋势和季节周期,用XGBoost捕捉残差中的非线性模式。
from statsmodels.tsa.statespace.sarimax import SARIMAX import warnings warnings.filterwarnings('ignore') # 步骤1:使用SARIMA模型 # 假设我们已经通过ACF/PACF图或自动定阶确定了SARIMA的阶数 (p,d,q)(P,D,Q,s) # 这里仅为示例,参数需要根据实际数据调整 order = (1, 1, 1) # (p,d,q) seasonal_order = (1, 1, 1, 12) # (P,D,Q,s) s=12表示日周期(以5分钟计,12*5min=1小时,这里假设日周期为288,示例简化) # 使用训练集数据拟合SARIMA train_series = df_detector_feat.iloc[:split_idx]['flow'] # 注意这里使用原始流量序列 sarima_model = SARIMAX(train_series, order=order, seasonal_order=seasonal_order, enforce_stationarity=False) sarima_result = sarima_model.fit(disp=False) # 在训练集和测试集上获取SARIMA的拟合值/预测值 # 这里获取的是训练集的一步预测拟合值 train_sarima_pred = sarima_result.get_prediction(start=0, end=len(train_series)-1).predicted_mean # 获取测试集的预测值(动态预测) test_sarima_pred = sarima_result.get_forecast(steps=len(y_test)).predicted_mean # 步骤2:计算SARIMA的残差 # 训练集残差 train_residuals = train_series.values - train_sarima_pred.values # 我们需要将残差与特征对齐。注意SARIMA预测可能从某个索引开始,需要对齐。 # 这里简化处理,假设索引已对齐。实际操作中需要仔细处理索引。 # 步骤3:用XGBoost预测残差 # 将残差作为新的目标值,使用相同的特征集(X_train, X_test)训练一个XGBoost模型 residual_model = xgb.XGBRegressor(**model.get_params()) # 使用相同参数 residual_model.fit(X_train, train_residuals) # 注意:这里X_train需要与train_residuals长度对齐 # 预测残差 train_residual_pred = residual_model.predict(X_train) test_residual_pred = residual_model.predict(X_test) # 步骤4:组合预测结果 train_combined_pred = train_sarima_pred.values + train_residual_pred test_combined_pred = test_sarima_pred.values + test_residual_pred # 评估组合模型在测试集上的表现 mae_combined = mean_absolute_error(y_test, test_combined_pred) r2_combined = r2_score(y_test, test_combined_pred) print(f"组合模型(SARIMA+XGBoost)测试集评估:") print(f" MAE: {mae_combined:.2f}") print(f" R²: {r2_combined:.4f}")通过对比单一XGBoost模型和组合模型的评估指标,我们可以验证融合策略是否有效。通常,组合模型在RMSE和R²上会有小幅提升,更重要的是其预测曲线会更加平滑,对趋势的把握更好。
5. 实战中遇到的典型问题与解决方案
在实际解题和后续的复盘过程中,我们遇到了不少坑。这里总结几个最具代表性的问题及其解决思路,希望能帮你避开这些雷区。
5.1 数据质量问题:隐形的“数据漂移”
问题描述:在模型训练期表现良好,但预测未来某段时间时误差突然增大。检查发现,那段时间正在进行道路施工,交通模式发生了根本性改变(数据分布漂移),但我们的模型没有感知。
解决方案:
- 数据标注与分段建模:如果能够获取外部信息(如施工公告、天气、事故报告),可以将这些时段标注出来。在训练时,可以尝试两种策略:一是将这些“特殊时段”的数据从训练集中剔除,专注于学习“正常模式”;二是将其作为一个特殊的特征(如
is_construction=1)加入模型,让模型学习不同模式下的映射关系。 - 在线学习或模型更新:对于长期部署的系统,需要建立模型性能监控机制。当检测到预测误差持续超过阈值时,触发模型在最新数据上的增量更新或重新训练,使模型适应新的交通模式。
- 使用对分布变化更鲁棒的模型:一些集成方法(如随机森林)和基于残差学习的模型(如我们用的SARIMA+XGBoost组合)对一定程度的数据漂移相对不敏感。
5.2 预测结果的“滞后”现象
问题描述:预测曲线与真实曲线形状相似,但总是“慢半拍”,特别是在交通状态发生剧烈变化(如拥堵开始或消散)时。这通常是模型过于依赖历史数据,对突变反应不足。
解决方案:
- 引入实时或近实时特征:除了历史交通参数,能否引入其他来源的实时数据?例如,来自导航软件的实时路况概览(拥堵指数)、相邻关键路口的信号灯状态、甚至社交媒体上关于交通事件的简短报告(需要文本挖掘)。这些特征可能比历史流量更早地预示变化。
- 调整模型关注度:对于LSTM类模型,可以调整网络结构,例如使用注意力机制(Attention),让模型在预测时能更关注与当前时刻最相关的历史时刻,而不是均匀地看待所有历史信息。
- 优化损失函数:在训练回归模型时,常用的均方误差(MSE)对所有误差一视同仁。可以尝试使用Huber损失或自定义损失函数,对“突变点”处的预测误差给予更大的惩罚,迫使模型学会更快速地响应变化。
5.3 多步预测精度衰减
问题描述:预测未来第1个时间点(5分钟后)精度很高,但预测第3个(15分钟后)、第5个(25分钟后)时间点时,误差显著增大。这是多步预测的经典难题。
解决方案:
- 滚动预测(Rolling Forecast):不直接一次性预测未来多个点。而是采用“预测-更新-再预测”的滚动方式。即先预测t+1时刻,假设这个预测值是准确的,将其作为已知数据加入历史序列,再预测t+2时刻,依此类推。这种方法更符合实际应用场景,但误差会逐步累积。
- 多输出模型(Multi-output Regression):直接训练一个模型,其输出层有多个神经元,分别对应未来要预测的多个时间点(如t+1, t+2, t+3)。XGBoost可以通过设置
objective='reg:squarederror'并构建特定的多列目标值数据集来实现。深度学习模型(如LSTM)可以自然地将最后一个时间步的隐藏状态映射到多个输出。这种方法能让模型同时学习多个未来时间点之间的相互关系。 - 序列到序列模型(Seq2Seq):这是处理多步预测的强大框架,尤其适用于深度学习。编码器(Encoder)将历史序列编码为一个上下文向量,解码器(Decoder)根据这个向量逐步生成未来的预测序列。这种方法非常适合输出序列较长的情况。
5.4 模型复杂性与可解释性的权衡
问题描述:使用了复杂的深度学习模型(如多层LSTM),虽然预测指标稍好,但在论文中难以解释其内部决策逻辑,评委可能会质疑其“黑箱”特性。
解决方案:
- 重视可解释性工具:即使使用复杂模型,也要辅以可解释性分析。对于树模型(XGBoost),可以绘制特征重要性(Feature Importance)图,清晰展示哪些特征(如“上一时段流量”、“小时”、“是否周末”)对预测贡献最大。对于深度学习模型,可以使用诸如SHAP(SHapley Additive exPlanations)值等工具,为单个预测样本提供特征贡献度解释。
- 采用“白盒”+“黑盒”组合:这正是我们选择“SARIMA + XGBoost”的原因之一。SARIMA部分(白盒)有清晰的统计学解释,可以分析其自回归项、移动平均项和季节项;XGBoost部分(黑盒)则通过特征重要性来辅助解释。在论文中,这种组合既展示了高级建模技巧,又体现了对模型可解释性的考量。
- 进行详尽的消融实验:在论文中,通过设计消融实验来证明每个部分的价值。例如,可以对比:a) 只用SARIMA;b) 只用XGBoost;c) SARIMA+XGBoost组合。通过表格清晰展示各项指标(MAE, RMSE, R², MAPE)的对比,用数据说话,证明组合模型的优越性,这本身就是一种强有力的解释。
最后,我想分享一点个人体会:数学建模竞赛,尤其是像交通预测这类贴近实际的应用题,比拼的不仅仅是模型的复杂度,更是解决问题的完整逻辑和严谨性。从数据清洗的每一步理由,到特征构建的每一个思考,再到模型选型的对比分析,最后到结果的可视化与误差的深入剖析,形成一个完整、自洽的闭环。你的论文要让评委看到,你不仅会“用”模型,更理解数据、理解问题、理解每一个选择背后的“为什么”。这才是获得高分的关键。