尧图精选

从美赛C类论文学数据分析:时间序列预测与特征工程实战

🕒 发布时间:2026/9/17 7:48:13 📁 来源:尧图网络
简介2023年美赛C类获奖论文《通过数据分析揭示Wordle的秘密》以热门游戏Wordle为对象完整展示了从数据处理、特征定义到预测建模的数学建模全流程适合美赛备赛者、数据分析学习者及相关方向师生研读。资源为单个PDF文件大小5.74MB完整收录论文全文涵盖GRU与GSRF预测模型、属性分析与难度分级等核心内容。目前已有195人学习下载。论文价值集中体现在建立GRU预测模型预测2023年3月1日报告数量相对误差率仅2.1569%定义词频、字母频率之和、字母重复模式及主要词性四个属性通过回归与箱形图分析其与得分的关系开发GSRF模型预测单词EERIE的得分百分比分布并借助K-Means完成难度分级为纽约时报编辑提供数据支持。整体可作为时间序列预测与数据挖掘案例参考。1. 读标题先读 C 类数据洞察题为什么是 IT 人最该精读的竞赛论文2023年美赛获奖C类论文_2300348.pdf这个文件名里藏着两组信息“C 类题”和“控制号 2300348”。MCM/ICM 的 C 类题对应官方定义的 Problem C数据洞察问题每年给一份真实数据集要求队伍在四天内完成从数据清洗、特征提取、建模预测到报告输出的完整闭环。它和 A/B 类最大的差异是不靠机理方程炫技而是靠“把数据讲明白”。对天天写代码、跑数据的工程师来说虽然不一定要参加数学建模竞赛但这类获奖论文完全可以当成一份被评审验证过的数据分析述职报告来读里面的假设处理、特征构造、验证方式和写作结构几乎每一段都能映射到日常的数据工作。后面几章就从这份 PDF 的常见模板出发按“评审视角 → 代码复现 → 写作包装 → 工程落地 → 验证技巧”的顺序拆成一个可执行的分析方案。2. 控制号 2300348 的门道评审用三遍扫描读完一篇 C 类论文拿到一份获奖论文第一件事不是从第一页开始顺读而是站在评委角度模拟浏览路径。评审的第一遍只看摘要页模型有没有、结果量化没有、方法是否匹配数据特点。第二遍翻图表和结论第三遍才回到细节看公式和假设。所以获奖论文的真正顺序是按“摘要 — 结论 — 方法细节 — 附录”布置的用最短路径把核心结论暴露给读者。文件名里的 2300348 是报名时的控制号不影响评阅结果评阅只看论文本身。2.1 摘要页提前定胜负五个从句里藏着评审的五个问题评委读摘要的时间通常不超过一分钟。获奖级别的摘要普遍是五句话结构第一句定义“这个问题本质上是回归/分类/时间序列问题”第二句说明数据来源和处理口径第三句给出主模型第四句陈述量化结果第五句交代验证方式。五句话里只要有一个是空话比如“取得了很好效果”整篇可信度就会下降。我在复现赛题时通常把这五句话做成待填模板正文每完成一个分析步骤就往对应位置填一个数字最后摘要只保留带数字的句子。这套方法也适合用来审读手头的 PDF先看摘要里出现的数字是否都能在正文和附录找到出处找不到的往往就是薄弱环节。实操时可以用下面的脚本快速提取 PDF 第一页文本把摘要单独揪出来。import pdfplumber with pdfplumber.open(2023年美赛获奖C类论文_2300348.pdf) as pdf: page pdf.pages[0] text page.extract_text() print(text[:1200])按页抽取而不是按文件拼接是因为摘要排版通常独立成页直接打印第一页能最快看到关键数字。如果提取结果为空或乱码说明 PDF 是扫描件需要先跑 OCR这本身就是写作时要被记录的数据预处理环节。提取后建议把摘要里的每个数字单独摘到一张表里再在正文中逐个定位用来判断论文是否经得起“逐句互证”。2.2 正文六段式从问题重述到结论每一段都是在推销“可信”C 类获奖论文的正文主体高度模板化普遍是问题重述、模型假设、模型建立、模型求解、敏感性分析、结论与评价六段。很多第一次写论文的人会把问题重述写成抄题把结论写成“模型很优越”这两处都在浪费篇幅。正确的做法是问题重述用两句话把题目转译成“输入数据 输出目标”的形式结论部分只汇报三件事——预测精度、稳定性、与基线的差距。下面把各部分评审关注点和常见翻车点对齐。论文部件评审关注点常见翻车点Summary Sheet30 秒内能否抓住结论堆术语、无量化结果数据预处理口径是否清楚、缺失值是否交代删数据不做说明模型假设假设是否影响泛化只写假设不做代价分析求解过程是否有中间结果可视化只给最终输出图敏感性分析参数扰动下结果是否稳定只加 10% 扰动糊弄附录和数据声明代码与数据是否可复现给错路径、换数据重跑这张表的用处在于给自己定检查清单每完成一段就对照“评审关注点”那一列问一句有没有落实。获奖论文并不是没有缺点的论文而是把缺点用方案解释掉的论文所以敏感性分析和数据声明两栏在获奖作品里通常特别厚。2.3 C 类特有的考察点不是比较模型数量而是看分析深度同样是建模A/B 类可以靠机理方程取胜C 类核心是“从数据里找到别人没有注意到的结构”。所以获奖论文一般不会把三个模型并列丢给读者而是按“描述—推断—预测—诊断”四个层次递进。以 2023 年 C 题的 Wordle 结果数据为例数据集中每天有一组“1 次到 6 次、失败”的计数值直接做均值预测看不出所以然但如果先把它折算成比例分布再按星期、按单词字母结构拆开就会得到“游戏难度随时间漂移”这类可以继续建模的结论。换句话说C 类题的本质工程问题是特征工程加分布预测而不是在调参上堆时间。3. 用 Python 重建最小分析链路从 Wordle 数据到次日分布预测前面把评审逻辑梳理完之后这章开始动手复现。这里不做复杂模型只用 pandas、sklearn 和一个多小时能跑完的流程去还原获奖论文主干的核心能力把一组计数数据变成可解释的特征、可回溯的预测和可量化的误差。所有代码都可以在本机 Jupyter 或 VS Code 里直接跑输入是你自己的同结构 CSV。3.1 先把“每次尝试的计数”折算成比例分布读进 CSV 后的第一步不是建模而是确认“总数”这个口径。官方数据通常给出每天每种尝试次数的人数如果直接拿绝对数建模周日玩家少、周中玩家多这类流量效应会污染难度判断。所以先按行求和再把每列变成比例。import pandas as pd df pd.read_csv(wordle_daily.csv) day_cols [tries_1, tries_2, tries_3, tries_4, tries_5, tries_6] df[fail] pd.to_numeric(df.get(fail, 0), errorscoerce) for col in day_cols: df[col] pd.to_numeric(df[col], errorscoerce) df[total] df[day_cols [fail]].sum(axis1) for i, col in enumerate(day_cols, start1): df[fratio_{i}] df[col] / df[total] df[fail_ratio] df[fail] / df[total] df[win_ratio] 1 - df[fail_ratio] df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) print(df[[date, word, ratio_1, ratio_3, win_ratio]].head())参数与逻辑说明如下。errorscoerce会把文件里的空格、NA、-这类字符转成 NaN避免整列求和时报错如果确认数据干净可以去掉。sum(axis1)是按行求和得出当天总玩家人数。ratio_1到ratio_6是各尝试次数的占比win_ratio表示当天不含失败的比例适合看总体趋势。此时最该查的不是均值而是分母如果某天total比其他天少一个数量级大概率是数据缺失而不是真实玩家流失应按缺失值处理而不是把它当成实际波动。注意失败字段在历年数据里字段名不一致先执行print(df.columns)确认。3.2 特征工程把单词变成能解释难度的数值特征Wordle 数据的自变量不在结果列里藏在“答案单词”中。常用做法是抽取单词长度、去重字母数、重复字母数、元音数和生僻字母数。这个规则简单但解释性很强因为英语五字母词里重复字母会明显提升猜测难度生僻字母直接影响前两次命中的概率。VOWELS set(aeiou) RARE set(jqxyz) def word_features(word: str) - dict: letters list(str(word).lower()) return { n_unique: len(set(letters)), n_repeat: len(letters) - len(set(letters)), n_vowel: sum(ch in VOWELS for ch in letters), n_rare: sum(ch in RARE for ch in letters), has_repeat: int(len(letters) ! len(set(letters))), } feat_df df[word].apply(lambda w: pd.Series(word_features(w))) df pd.concat([df, feat_df], axis1) df[lag7_win] df[win_ratio].shift(7) df[rolling14_win] df[win_ratio].rolling(14).mean().shift(1) print(df[[word, n_unique, n_repeat, n_vowel, n_rare]].head())代码里.shift(7)取的是 7 天前的胜率.rolling(14).mean().shift(1)是最近 14 天的平均胜率再往后推一天。这样做的目的是避免数据泄漏第 t 天的特征里不能出现第 t 天当天或未来的结果否则回测数字会虚高。单词特征从答案文本直接生成不依赖外部词库。很多获奖论文的难度模型会进一步引入词频词典但真正带来增量的是这些统计量之间的关系。如果你的数据集里没有word字段也可以改用比赛序号的周期项比如对 7 取模生成星期指示变量。3.3 时序切分用 TimeSeriesSplit 而不是普通 K 折分布预测的时间序列属性要求验证集必须晚于训练集。如果随机切分模型相当于偷看了未来误差会失真。机器学习里对应工具是TimeSeriesSplit它把样本按顺序划分成多个前训练后测试的窗口。from sklearn.linear_model import LinearRegression from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error feature_cols [n_unique, n_repeat, n_vowel, n_rare, lag7_win, rolling14_win] X df[feature_cols].fillna(0).to_numpy() y df[win_ratio].to_numpy() tscv TimeSeriesSplit(n_splits4) for train_idx, test_idx in tscv.split(X): model LinearRegression() model.fit(X[train_idx], y[train_idx]) y_pred model.predict(X[test_idx]) mae mean_absolute_error(y[test_idx], y_pred) print(ftest_size{len(test_idx)} MAE{mae:.4f})n_splits4表示数据被切四次每次训练集都比上一次更长测试集是其后一段连续数据不会出现随机洗牌。这里刻意选择回归而不是分类是因为win_ratio连续方便直接比较。fillna(0)在这段代码里只用于流程演示正式比赛建议对前 14 天做剔除或用前向填充否则第一周的滞后特征会被错误置零。如果你要预测的是完整的七个桶输出层应换成多元回归或用 softmax 建模。3.4 用一张表记录模型迭代写论文时直接引用四天赛程里不太可能一路线性推到最后获奖更常见的是按“论文结论需要什么就补什么”的顺序迭代。建议从一开始就维护一张模型记录表最后的敏感性分析和摘要都能直接取材。阶段模型预测目标验证方式MAE 变化基线14 日滚动均值win_ratio最后 60 天回测0.031加入单词特征线性回归win_ratioTimeSeriesSplit0.026加入滞后特征线性回归win_ratioTimeSeriesSplit0.023换成分布预测多元 softmax七个桶CRPS0.019表格里的数字是演示用真实比赛要填自己回测的结果。这张表的价值不在于哪个模型最终胜出而在于每一项都能在正文找到对应实现。评委看到“从 0.031 降到 0.023”时会回溯训练方向。如果没有记录最后写摘要时容易编一个数字反而失分。4. 图表与摘要的写作技巧把分析过程包装成“可评审”的样子4.1 三张必做图堆叠分布图、趋势对比图和敏感性热力图C 类获奖论文中图表承载的信息量往往超过正文。第一张图是每天各尝试次数占比的堆叠图把“整个分布随时间漂移”的过程一次性可视化第二张图是win_ratio预测值与实际值对比用于展示误差第三张图是敏感性热力图把参数扰动幅度放在网格轴上。堆叠图用 matplotlib 就能做。import matplotlib.pyplot as plt import numpy as np plot_cols [fratio_{i} for i in range(1, 7)] [fail_ratio] df_sorted df.sort_values(date) fig, ax plt.subplots(figsize(10, 4)) colors plt.cm.Blues(np.linspace(0.35, 0.95, len(plot_cols))) ax.stackplot(df_sorted[date], *[df_sorted[c] for c in plot_cols], labels[1次, 2次, 3次, 4次, 5次, 6次, 失败], colorscolors) ax.legend(locupper left, ncol7, frameonFalse) ax.set_ylabel(占比) fig.autofmt_xdate() plt.tight_layout() plt.savefig(stacked_distribution.png, dpi200)plt.cm.Blues(np.linspace(0.35, 0.95, 7))生成同一色系不同亮度的七种颜色避免堆叠图显得刺眼。ncol7让图例单行排列配合frameonFalse去边框打印到论文里会明显比默认样式干净。保存时务必用dpi200MCM 有页数上限而图要清晰低分辨率图会被直接归入“不专业”。注意stackplot对传入顺序敏感顺序必须从 1 次到失败否则图例和面积颜色会错位。图类型用途正文配合点堆叠面积图展示构成随时间变化强调“第 4 次尝试占比升高”预测对比折线图展示回测误差标出 MAE 数值敏感性热力图展示参数稳健性配合“变化不超过 5%”的结论4.2 摘要写作的“反推法”摘要里每个数字都能在正文找到出处写作阶段最容易犯的错误是让摘要与正文脱节。常见做法是把摘要放在最后写并用反推法检查先看模型记录表里最后一项再决定摘要里放哪个数字。下面是一组对比。低分写法“采用多种方法分析了数据并建立模型结果表明模型效果较好。”改写后“把任务拆成难度估计和趋势修正两个子模块。基线用 14 日滚动均值预测失败率MAE 为 0.031加入单词长度、重复字母与滞后特征后MAE 降至 0.023在最后 60 天滚动验证中稳定优于基线。敏感性分析显示训练窗口从 120 天延长到 180 天时MAE 变化不超过 5%。”改写版每一句话都能对应代码或表格第一句对应问题重述第二句对应训练基线第三句对应特征工程和验证第四句对应敏感性分析。原则是“摘要里出现的每个数字正文和附录都要能找到”。这个方法还可以逆用快速鉴别一份 PDF 是否高质量就是看摘要里的数字能否在正文一一落地。4.3 敏感性分析用参数扰动展示稳健性而不是展示调参能力敏感性分析在 C 类评分里的权重超过许多人的预期。它回答的是“结论是否依赖某个特定设置”。参数扰动不只包括模型超参还包括前处理中的窗口长度、异常值阈值、缺失值填充方式。写成代码只是一个小循环。for shift in [5, 7, 10, 14]: df[flag_{shift}] df[win_ratio].shift(shift) results {} for shift in [5, 7, 10, 14]: col flag_{shift} tmp df.dropna(subset[col]).copy() mae mean_absolute_error(tmp[win_ratio], tmp[col]) results[shift] round(mae, 4) print(results)这段代码把滞后阶数从 5 到 14 分别测一遍看预测误差对窗口设置的敏感程度。需要写进论文的是最后两行结论比如“随窗口从 5 延长到 14MAE 从 0.026 变化到 0.024波动小于 8%”而不是“模型参数经过多轮调优后性能良好”。敏感性的解释要和机制挂钩窗口短意味着记忆近、波动大窗口长更平滑但反应慢。能够把这层机制写出来是获奖论文和作业论文的分界。5. 把比赛方法搬进系统从论文预测到监控告警的最小迁移5.1 预测分布而非预测均值让告警更敏锐论文里预测的是第二天的分布落地成工程系统时这个能力天然适合做异常检测。假设你有一个模型每天输出七个桶的预测概率pred_probs当天线上回传的实际结果是七个计数换算成比例后可以看右侧尾部概率。def check_daily_prediction(actual_counts, pred_probs, tail_index(4, 5, 6)): actual np.array(actual_counts, dtypefloat) actual / actual.sum() actual_tail actual[list(tail_index)].sum() pred_tail pred_probs[list(tail_index)].sum() ratio actual_tail / max(pred_tail, 1e-9) return { pred_tail: round(pred_tail, 3), actual_tail: round(actual_tail, 3), tail_ratio: round(ratio, 2), alert: actual_tail max(0.2, pred_tail * 1.3), }这里的tail_index(4, 5, 6)对应“第 5 次、第 6 次、失败”三个尾部桶。如果尾部实际占比超过预测的 1.3 倍说明当天难度被系统低估。max(0.2, …)给低频场景加了下限避免小流量日频繁误报。把这段逻辑包成接口后每天只需保留一个alert字段。相比只看均值分布视角对难度突增的反应更快这正是论文方法在工程上的直接价值。5.2 概念漂移历史数据训练出的模型要不要重新训练Wordle 难度在宏观上会随词汇库变化这种数据分布随时间改变的现象就是概念漂移。比赛论文中常把窗口长度当成超参而在工程系统里它应该变成在线策略。常见做法是保留最近 N 天重训N 的选择直接影响时效性。def rolling_refit(data, window180): train data.iloc[-window:] model LinearRegression() model.fit(train[feature_cols].fillna(0), train[win_ratio]) return model.coef_, model.intercept_window180是在“用到足够多数据”和“快速适应新难度”之间取平衡。判断是否合适的方法是回测不同窗口下的 MAE如果两个窗口结果几乎一样说明数据漂移不显著没必要为刷指标频繁重训。这里的常见误是把整段历史全部塞进去训练结果旧难度分布把模型拖住预测永远滞后。建议每天重训时只保留上一轮模型的特征均值和窗口期末状态而不是保留全量样本。5.3 生产迁移中的三个典型问题论文环境和生产环境有一个明显差异论文可以用 pandas 直接操作整张表生产则要考虑数据延迟、字段更名和阈值漂移。下面是我在同类项目里会列入迁移清单的三项。问题现象检查方式数据口径变化失败字段从fail改成lost启动时跑 schema 校验器小流量日分母抖动total只有几十尾部比例剧烈振荡按 7 日平均重算对低流量日跳过告警预测概率失真模型输出分布与长期统计不一致每月统计一次 CRPS这三条都不是新理论但竞赛论文不会写。把获奖代码搬进生产时能直接复用的是模型逻辑不能复用的是缺失行处理和回测方式因为生产环境的坑大多长在数据链路上而不是模型内部。迁移前先在本地用生产数据备份跑一次全量回测再决定保留哪些部分。6. 用离散 CRPS 校准分布预测比 RMSE 更诚实的验证技巧比赛里大多数人用 MAE 或 RMSE 验证回归模型但在预测“七个桶分布”的 C 类题里这两个指标只评估了平均值丢掉了分布形状。评委真正想看到的是模型预测的是一个高瘦分布还是一个平坦分布。CRPS连续排序概率评分衡量整条累积分布曲线与观测值的差距分布形状越准分数越低。6.1 有序类别上的 CRPS 计算与解读def crps_ordinal(pred_probs, obs_index): pred_cdf np.cumsum(pred_probs) obs_cdf np.zeros(len(pred_probs)) obs_cdf[obs_index:] 1.0 return float(np.mean((pred_cdf - obs_cdf) ** 2))pred_probs是对七个桶预测的概率顺序必须是“第 1 次、第 2 次、…、第 6 次、失败”obs_index是当天实际结果的桶下标比如当天答案是“第 3 次尝试猜中”时传入 2。代码先把预测概率转成累积分布函数再把观测转成阶跃函数两者逐点求差平方后取平均。桶宽默认相等的假设下这个均值等价于概率积分距离不需要额外缩放。举个例子真实观测是第 3 次完成obs_index2基线模型大概率给出平坦分布加强模型给出尖峰分布。前者的 CRPS 一般高于后者说明它没有识别出当天难度偏低。这个指标可以直接放进论文的模型对比表也可以在生产环境里按周滚动统计用来发现系统输出分布和线上实际长期不一致的问题。由于它对有序类别天然敏感比单独比较每个桶的误差更能体现分布预测的优劣。你甚至可以用同一个函数验证两个不同模型的预测得到一张 CRPS 表放到敏感性分析部分——这比在文字里反复说“分布拟合良好”有说服力得多。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →