☰
美赛C题获奖论文拆读:数据预处理到五层模型的完整实战
2026/10/22 2:35:55 网站建设 项目流程

简介:这份2023年美赛C题获奖论文(编号2309397)聚焦热门文字游戏Wordle的结果数据,系统展示从数据清洗、时间序列分析到ARIMA-BP神经网络区间预测、多因素线性回归、LSTM结果分布预测、GMM难度聚类及Apriori关联规则挖掘的完整建模链路,并给出MSE、RMSE、R²等误差指标与COV不确定性度量。资源面向备赛美赛及其他数学建模竞赛的队伍,适合学习如何构建混合模型、处理百分比约束数据、开展敏感性分析与写作获奖级论文。包体为1个PDF文件,压缩后约1.36MB,可单独下载、便于离线阅读。目前已有143人学习下载。论文还包含对EERIE等单词的具体预测示例、难度分级与字母频率统计结论,并对纽约时报编辑提出建议,内容完整、图表齐全,可作为建模思路与写作范本。

1. 2023年美赛C题获奖论文拆读:一份数据拆出五层模型的完整样板

美赛C题向来是数据分析题的主战场:题面看似简单,真正拉开分差的往往是数据预处理、模型组合逻辑和结果解释能力。这篇2023年C类获奖论文,研究对象是某款每日一题的单词游戏——所有玩家每天猜同一个五个字母的单词,数据记录了2022年全年每天的上报人数、尝试次数分布和当日单词。它把这份数据拆成了五个递进任务:区间预测、属性回归、分布预测、难度聚类、关联规则挖掘,从ARIMA-BP混合模型一路做到GMM聚类和Apriori算法,每一层都围绕同一份数据展开,思路完整度很高。适合美赛参赛者、时间序列建模入门的同学,以及想看看“获奖论文怎么组织数据挖掘流程”的从业者。我拆完这份PDF之后把它系统性梳理成下面的笔记,顺便把每一处能直接照抄的参数和坑都标了出来。

2. 数据预处理与ARIMA-BP区间预测:异常值清洗与Bootstrap置信带的落地

这一章对应论文的Task 1,也是全文最值得细读的部分——它的核心并不是“用了ARIMA”,而是“怎样把异常值处理、三阶段划分、组合预测、区间估计串成一条可复现的流水线”。

2.1 数据清洗:三行删除与两处改值背后的判断逻辑

论文的数据预处理表里列得很清楚,原始数据存在五类异常:

  • 三个单词记录无效:marxh、clen、tash,这三个词要么不存在,要么长度不是5,真值无法恢复,论文直接整行删除;
  • 一处数值漏位:某条上报数量为2569,结合前后日期和数量级判断应为22569,论文做了修正;
  • 一处拼写错误:rprobe应为probe,上下文语义可推断,论文做了替换。

这个处理策略实际上给了一个非常实用的判断框架:先判断异常值的“真值是否可以恢复”。可以恢复——结合前后记录、数量级、语义推断后修改;不能恢复——直接删除整行,避免编造数据影响后续模型。

用Pandas做一遍同样的清洗,常见做法是这样:

import pandas as pd df = pd.read_csv("wordle_daily.csv") # 1. 单词列必须是5个字母且全部为小写字母 mask_invalid_word = ( (df["word"].str.len() != 5) | (~df["word"].str.match(r"^[a-z]{5}$")) ) # 2. 上报数量列明显偏离正常数量级的记录 mask_unusual_num = df["reported_results"] < 1000 # 打印两类异常的位置,人工确认后再处理 print(df[mask_invalid_word | mask_unusual_num]) # 3. 人工确认后删除无效词行 df = df.drop(index=df[mask_invalid_word].index) # 4. 人工确认后修正数值漏位与拼写错误 df.loc[df["reported_results"] == 2569, "reported_results"] = 22569

这里的要点是:正则^[a-z]{5}$只匹配小写字母开头的五字母单词,能够把长度错误、包含数字或空格的脏数据一次性筛出来;数值列低于1000的判断阈值并不是拍脑袋,而是要结合序列的整体数量级来定,论文中的上报数量峰值超过36万,几百的数显然不合理。

另外要注意,论文提到原始数据中各行百分比之和分布在98%到102%之间。这属于数据发布时四舍五入造成的精度损失,不是录入错误,所以论文没有强行归一化——这个处理在后续LSTM成分预测那一步会被球坐标变换天然吸收。

2.2 三阶段划分:游戏生命周期是时间序列预测的定海神针

论文对2022年全年上报数量的时序图做了观察,把变化分成三个阶段,并给出了每阶段的量化幅度:

  • 快速增长期(1月7日 - 2月2日):整体增长348.85%,峰值出现在2月2日,约361908条;
  • 快速衰退期(2月3日 - 5月29日):整体下降84.29%,对应互联网热潮消退;
  • 稳定减少期(5月30日 - 12月31日):整体下降64.14%,但降幅趋缓,剩下的主要是粘性用户。

这里值得关注的不只是阶段划分本身,而是它为什么要这样划分。ARIMA模型要求输入序列具备平稳性,而完整的原始序列明显非平稳——不仅均值在变,方差也在变。强行对整个序列拟一个ARIMA,残差会包含大量趋势信息,模型诊断很难通过。论文的做法是把序列当成一个生命周期系统来处理:增长期、衰退期、稳定期各用各的特征去理解,ARIMA负责捕捉各段内部的线性趋势和波动结构,BP神经网络再负责拟合ARIMA残差里没有消化的非线性部分。

用ADF检验判断是否需要差分,是这一步的常见收尾动作:

from statsmodels.tsa.stattools import adfuller series = df["reported_results"].values # 对原始序列做ADF检验 result = adfuller(series) print(f"ADF p-value (raw): {result[1]:.4f}") # 一阶差分后再检验 diff_series = series[1:] - series[:-1] result_diff = adfuller(diff_series) print(f"ADF p-value (diff): {result_diff[1]:.4f}")

ADF检验的零假设是序列存在单位根、即非平稳。p值小于0.05时拒绝原假设,认为序列平稳。论文最终选择的ARIMA模型中含有一阶差分项,和这一步的试探方向一致。

2.3 ARIMA-BP组合与Bootstrap区间估计

组合模型的结构很清晰:ARIMA做线性基线预测,BP神经网络接收滞后特征并拟合ARIMA的残差,两者加权后得到最终点预测。

用符号表示就是:

y_hat = wA * y_arima + wB * y_bp

其中 wA、wB 分别是ARIMA和BP的权重,和为1。这个设计的价值在于:ARIMA对趋势的提取能力是成熟的,但面对突变拐点(比如2月2日前后从暴增转为暴降)响应偏慢;BP神经网络虽然对非线性拟合能力强,但单独用容易过拟合噪声。组合之后,ARIMA负责“大方向”,BP负责“追残差”,这正是时间序列混合模型最常见的搭法。

区间预测部分,论文用了Bootstrap方法:对ARIMA残差进行有放回重采样,生成大量“点预测 + 随机残差”的模拟样本,取2.5%和97.5%分位数,得到95%置信区间。这个思路不依赖残差正态分布的假设,对实际数据更稳健。

import numpy as np from statsmodels.tsa.arima.model import ARIMA # 取最后20天作为测试段,前面的数据用来拟合 train = series[:-20] test = series[-20:] # ARIMA基线:p=2, d=1, q=2 为常用起点 model = ARIMA(train, order=(2, 1, 2)) fit = model.fit() pred_arima = fit.forecast(steps=len(test)) resid = fit.resid # BP网络部分省略:输入为滞后7天的窗口特征,输出为ARIMA残差 # pred_bp = bp_model.predict(lag_feature_matrix) # 组合预测 wA, wB = 0.6, 0.4 y_point = wA * pred_arima + wB * pred_bp # Bootstrap重采样 B=1000 次 B = 1000 sampled = [] for _ in range(B): boot_noise = np.random.choice(resid, size=len(test), replace=True) sampled.append(y_point + boot_noise) sampled = np.array(sampled) lower = np.percentile(sampled, 2.5, axis=0) upper = np.percentile(sampled, 97.5, axis=0) print(f"3月1日区间预测: ({lower[-1]:.2f}, {upper[-1]:.2f})")

这里要说明几点:ARIMA的order=(2,1,2)是受论文启发选择的起点,实际操作中建议用AIC/BIC做网格搜索确定最佳p和q;Bootstrap的B值取1000是通用经验值,B越大区间越稳定,但计算成本线性增加;wA=0.6, wB=0.4是我按“ARIMA主导、BP修正”的常规配比给的示例,论文原文的权重通过训练集优化得到。

论文最终给出2023年3月1日的95%置信区间约 (19504.74, 20383.26)。这个数字本身不是重点,重点是区间从哪来——它来自Bootstrap对残差不确定性的量化,而不是简单地对点预测加减一个标准差。

3. 单词属性与结果分布建模:多元线性回归里的0.618与LSTM球坐标变换

这一章把论文的Task 2和Task 3放在一起看,因为它们共享同一套自变量——单词属性。区别只在于因变量:一个是硬模式玩家比例(回归问题),一个是1-7次尝试的百分比分布(成分数据预测问题)。

3.1 三个定性属性与四个定量属性的定义思路

论文定义了3个定性属性和4个定量属性来刻画每个单词。定性属性包括首字母是否为元音、是否含重复字母这类“是/否”特征;定量属性则包括字母内部距离、字母常见度等连续值。论文没有展开每个指标的具体计算式,但按常见做法,内部距离可以定义为相邻字母在字母表中位置差的绝对值之和。

以EERIE为例,五个字母在字母表中的位置分别是5、5、18、9、5,内部距离计算:

def word_internal_distance(word): dist = 0 for i in range(len(word) - 1): dist += abs(ord(word[i + 1]) - ord(word[i])) return dist print(word_internal_distance("EERIE")) # 26 print(word_internal_distance("ABOUT")) # 相邻位置差累计后远小于26

EERIE的内部距离为26,属于偏大的数值,说明它的字母在键盘/字母表上跨度大,视觉上不紧凑,猜起来需要更多轮信息反馈。这个特征在后面GMM难度聚类中会再次发挥作用。

3.2 多元线性回归:首字母元音与内部距离对硬模式比例的影响

论文用7个属性作为自变量,硬模式玩家比例作为因变量,构建多元线性回归。两个核心结论:

  • 单词首字母从元音变为辅音时,硬模式玩家比例平均下降0.618;
  • 单词内部距离每增加1个单位,硬模式玩家比例平均增加0.017。

这两个系数方向都有直观解释。元音开头的词更容易被玩家用“元音试探策略”开局(先猜一个富含元音的词再逐步收敛),这种策略在硬模式下依然有效,因此硬核玩家更愿意留在硬模式;而辅音开头的词往往需要大量试错,普通玩家和硬核玩家的玩法差异被稀释,硬模式占比反而下降。内部距离大的词整体更难,挑战性更强,主动选硬模式的玩家比例也随之上升。 用statsmodels可以复现回归:

import statsmodels.api as sm # 特征:首字母是否元音(虚拟变量)、内部距离、重复字母比例等7个属性 X = df[["is_vowel_first", "internal_distance", "dup_ratio", "letter_freq_score"]] y = df["hard_mode_pct"] # 加截距项 X = sm.add_constant(X) model = sm.OLS(y, X).fit() print(model.summary()) # 输出中: # is_vowel_first 的系数约为 -0.618 (元音→辅音导致硬模式比例下降) # internal_distance 的系数约为 +0.017 (距离每+1, 硬模式比例+0.017)

注意is_vowel_first这一列的编码方式:如果是“1=元音,0=辅音”,系数应该为正,因为论文说的是“从元音变辅音时下降0.618”;如果编码反过来,符号就相应反转。这里最容易踩坑,后面避坑章节会再提。

3.3 LSTM分布预测与球坐标变换

Task 3要预测每个单词在1-7次尝试上的百分比分布,这是一个典型成分数据(compositional data)预测问题:输出向量的每个分量非负,且总和必须接近100%。直接对7个百分比分别做回归,模型根本不知道“和为100%”这个约束,预测结果大概率会破约束。

论文的处理方式是球坐标变换。思路如下:把一个7维的成分向量归一化到和为1后,看作高维球面上的一个点;用1个径向长度和6个角度把它的几何位置完全刻画。预测时只预测6个角度,径向固定为归一化后的常数,再反变换回7维成分空间,输出天然满足归一化约束。

import numpy as np def comp_to_spherical(x): x = np.asarray(x, dtype=float) x = x / np.sum(x) # 归一化,确保分量和为1 r = np.sqrt(np.sum(x ** 2)) theta = [] for i in range(len(x) - 1): s = np.sqrt(np.sum(x[i:] ** 2)) theta.append(np.arccos(np.clip(x[i] / s, -1.0, 1.0))) return r, theta def spherical_to_comp(r, theta): x = np.zeros(len(theta) + 1) s = r for i, t in enumerate(theta): x[i] = s * np.cos(t) s = s * np.sin(t) x[-1] = s return x / np.sum(x) # 示例:论文对EERIE的预测分布 comp = np.array([0.02, 0.11, 0.25, 0.24, 0.19, 0.14, 0.05]) r, theta = comp_to_spherical(comp) back = spherical_to_comp(r, theta) print(np.round(back, 4)) # 反变换后仍然满足和为1

这个变换的关键在于:LSTM在无约束空间中做回归,学习6个角度的时序变化规律,而不是直接学习7个有约束的分量。最后的反变换负责把约束“装回来”。相比在神经网络输出层接一个softmax,球坐标变换的优势是它改变了预测目标本身的几何形态——角度序列比原始百分比序列更容易被LSTM捕捉长期依赖。

论文输入特征包括7个单词属性和当天上报数量,输出为变换后的角度序列,最终对EERIE的预测分布为 [2%, 11%, 25%, 24%, 19%, 14%, 5%]。从分布形态看,峰值集中在3次和4次尝试,符合中等偏上难度单词的猜测曲线特征。模型不确定性用变异系数COV衡量,论文计算约为0.4——这个值反映了模型在不同参数和噪声扰动下预测的波动幅度,0.4在成分数据预测里属于中等水平,说明预测可用,但还没有到高度稳定的程度。

4. 避坑与常见问题:Wordle类数据建模反复踩到的五个坑

数据拿到手之后,最容易出问题的并不是模型选型,而是数据理解、约束处理和参数调优这些“看不见的细节”。下面是这类题目里高频出现的五类问题,每一条我都结合论文的处理方式写了现象、原因和解决路径。

4.1 数据质量与预处理阶段的坑

坑一:遇到异常值不知道先删还是先改

现象:原始数据里出现不存在的词、长度异常的单词、数量级离谱的数字,删了怕样本量不够,改了又觉得自己在“造假”,最后直接带入模型,导致预测结果被极值污染。

原因:异常值处理没有统一的判断标准,很多人下意识按“能不能凑合”来决策。

解决:先判断“真值是否可以恢复”。能恢复——结合上下文和数量级推断后修正;不能恢复——删除整行。论文对marxh、clen、tash直接删行,是因为这些词的真值无法从任何字段恢复;对2569改成22569、rprobe改成probe,是因为可以通过相邻记录和语义推断。这个原则适用于任何比赛数据,不只是词谜游戏。

坑二:百分比之和不是100%,直接归一化反而引入误差

现象:原始数据里每天各尝试次数的百分比加起来是98%或102%,有人为了让数据“好看”强制按比例放大或缩小。

原因:这是数据发布时四舍五入到整数造成的正常误差,不是录入错误。强行归一化会把舍入误差放大成系统性偏差。

解决:论文直接接受这个误差范围,不做处理。如果后续模型对“和为100%”有硬约束(比如成分数据预测),交给球坐标变换或对数比率变换去吸收,不要在预处理阶段手动拉齐。

4.2 建模与评估阶段的坑

坑三:ARIMA模型定阶全凭肉眼,主观性太强

现象:ACF和PACF图怎么看都像“截尾+拖尾”的混合形态,不同人定出的p和q能差出好几个档位,模型结果也随之大幅波动。

原因:ARIMA定阶本身存在主观判断空间,尤其在小样本序列上ACF/PACF特征不明显。

解决:用AIC/BIC网格搜索自动定阶,再用ADF检验确定差分阶数d。本文第2章的代码里给的是order=(2,1,2)作为人工起点,实际工程中我会先跑一轮(p, d, q)的网格搜索,选AIC最小的组合;同时保留BP神经网络去拟合ARIMA残差,这样即使定阶不是全局最优,非线性部分也能被BP补回来。

坑四:LSTM直接预测百分比分布,结果出现负值或者总和不是100%

现象:模型输出的7个百分比有负数,或者加起来是110%,怎么看都不像合理的分布。

原因:LSTM是回归模型,它不知道输出向量必须满足“非负且求和为1”的成分约束。

解决:先做球坐标变换再预测,最后反变换回成分空间。论文的做法是改造预测目标,而不是在输出层强行加归一化。如果你不想用球坐标,也可以用CLR(中心对数比率)变换,效果等价,只是几何直观性稍差。

坑五:GMM聚类的分组数选不准,难度分级结果不稳定

现象:同样的特征输入,K=4和K=6时聚类结果差异巨大,而且每个簇的语义不好解释。

原因:K是超参数,数据没有标准答案。对Wordle难度分级来说,“中等难度”本身就是一个连续谱,切成几段各说各话。

解决:用BIC或轮廓系数选择K,然后把每个簇的平均特征打印出来做人工标注。论文最终选K=5,结合经验把簇解释为五级难度,EERIE分到第三级——这个“第三级”背后是“中等偏上”的语义映射,而不是聚类的客观结论。

5. 把GMM难度分级与Apriori关联规则做成可复用的分析套路

论文的Task 4和Task 5属于探索性分析,对应的技术栈是GMM聚类和Apriori关联规则。这两个方法单独看都不新鲜,但放在同一份数据上串成“先聚类、再关联”的分析链路,可以抽出一个通用套路:先定义一组可计算的原始特征,标准化后进聚类模型得到分箱标签,再做关联规则挖掘发现共现模式。

难度分级用的是6个单词难度指标,论文命名为RDC、TE、SK、NFC、NON、HL。这些指标的具体定义需要回到原文表里确认,但建模套路是通用的——特征进GMM之前必须标准化,否则不同量纲的特征会主导距离计算;聚类树数用BIC选,而不是拍脑袋定几类。下面的代码是常见实现方式:

from sklearn.mixture import GaussianMixture from sklearn.preprocessing import StandardScaler from mlxtend.frequent_patterns import apriori, association_rules # 1. 特征标准化 features = df[["RDC", "TE", "SK", "NFC", "NON", "HL"]] scaler = StandardScaler() X_scaled = scaler.fit_transform(features) # 2. GMM聚类,分量数用BIC选,这里以5为例 model = GaussianMixture(n_components=5, random_state=42) df["difficulty_level"] = model.predict(X_scaled) # 3. 关联规则:把单词拆成字母集合 letters = df["word"].apply(list) itemset = pd.get_dummies(pd.DataFrame(letters.tolist()).stack()).groupby(level=0).sum() itemset = (itemset > 0).astype(bool) freq = apriori(itemset, min_support=0.05, use_colnames=True) rules = association_rules(freq, metric="lift", min_threshold=1.0)

GMM里的n_components=5对应论文的五级难度体系;min_support=0.05表示某个字母组合至少出现在5%的单词中才进入候选集;lift > 1.0表示组合出现的概率高于各字母独立出现的概率,提升度越大,共现的非随机性越强。论文的结论中,A、S、E经常同时出现,F、T、L倾向于结伴,本质上就是从频繁项集里找出的高提升度规则。

这套流程的价值在于可迁移。无论是商品销售数据、日志事件序列,还是文本字频统计,只要把“字母”换成“项目”,把“单词”换成“事务”,Apriori这层就能直接复用;GMM的分组语义也始终遵循同一逻辑——先标准化、再聚类、最后人工标注。对于参赛来说,这意味着你不必为每道题重新设计模型,只需要把特征定义换成题目语境里的新指标。

从那以后,我每次拿到成分数据或者需要做分组预测的任务,都会强制走一遍三件事:先检查百分比/分组约束是否被模型感知,再确认聚类特征是否做了标准化,最后把BIC和提升度两个指标打印出来看一眼再写结论。这套流程帮我避掉了不少“看着对、一提交就出问题”的状况。希望帮到你。

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

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

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

立即咨询