资讯动态

2023美赛C题Wordle数据分析与预测模型实战

发布时间:2026/10/3 9:35:28 来源:尧图企业网站定制
简介这份资源是2023年美国大学生数学建模竞赛C题的完整备赛资料包面向准备参加MCM/ICM的高校学生及指导教师尤其适合需要系统复盘C题建模思路、补齐数据处理与论文写作短板的参赛者。包内共208个文件涵盖49个pdf、35个xlsx、34个zbak、32个png、20个txt、15个docx及sav、spv、fig等格式分别对应赛题分析文档、数据表格、程序备份、结果图表与统计脚本压缩包约59.42MB。目前已有176人学习下载。资料围绕C题展开包含数据平滑滑动窗口、回归计算、聚类分析K-Means、拟合公式推导、单词属性分析等具体模块可帮助读者快速理解题目背景、复现建模流程、验证模型结果并参考论文结构完成摘要、假设、求解与检验等章节的撰写适合作为赛前练习与论文写作的对照素材。1. 2023年美赛C题到底在考什么从Wordle数据到预测决策的完整链路2023年美国大学生数学建模竞赛C题给了一批Wordle玩家的游戏记录要求分析玩家行为、预测未来结果并给出决策建议。这道题看起来是数据分析实际上考的是从原始日志到可落地策略的全链路能力。很多队伍拿到数据就开始跑分类模型结果发现准确率卡在某个瓶颈上不去问题出在没想清楚“预测什么”和“为什么能预测”。Wordle的每日答案只有两千多个候选词玩家每猜一次都在缩小范围这个约束条件本身就是最强的特征。适合谁看如果你正在准备数学建模竞赛或者手头有用户行为日志想做预测和策略优化这篇笔记会从数据理解、特征构造、模型选型到结果验证把每一步的参数和坑都讲清楚。热搜词“Wordle数据分析”和“数学建模预测模型”在这里不是标签而是贯穿全文的具体操作对象。2. 把Wordle日志拆成可建模的三张表数据清洗与特征工程2.1 原始数据长什么样先别急着跑模型2023年美赛C题的数据通常包含三个文件玩家猜词记录、单词属性表、以及每日答案对应关系。猜词记录里每一行是一次尝试字段包括玩家ID、猜词日期、猜测的单词、以及每个字母的反馈状态绿色正确位置、黄色存在但位置错、灰色不存在。单词属性表包含单词本身、词频、字母组成等信息。答案表则是日期到目标单词的映射。我一般会先把这三张表的关系画清楚玩家ID和日期构成联合主键单词是外键关联到属性表。很多队伍直接拿猜词记录做聚合忽略了单词属性表里的词频和字母分布导致特征维度少了一大截。常见做法是先把三张表合并成一张宽表每行代表“某玩家在某天的一次猜测”列包括猜测序号、反馈编码、目标词长度、目标词词频等。提示合并前先检查玩家ID和日期的唯一性Wordle允许同一天多次猜测但比赛数据通常已经按玩家去重如果发现重复要先确认是数据问题还是业务逻辑。2.2 用Python做数据清洗和基础特征构造import pandas as pd import numpy as np # 读取三张表 guesses pd.read_csv(guesses.csv) words pd.read_csv(words.csv) answers pd.read_csv(answers.csv) # 合并单词属性 guesses guesses.merge(words, left_onguess_word, right_onword, howleft) # 合并答案信息 guesses guesses.merge(answers, ondate, howleft) # 构造基础特征 guesses[attempt_num] guesses.groupby([player_id, date]).cumcount() 1 guesses[word_length] guesses[guess_word].str.len() guesses[is_correct] (guesses[guess_word] guesses[target_word]).astype(int) # 反馈编码绿色2黄色1灰色0 def encode_feedback(row): feedback [] target row[target_word] guess row[guess_word] for i, ch in enumerate(guess): if ch target[i]: feedback.append(2) elif ch in target: feedback.append(1) else: feedback.append(0) return feedback guesses[feedback] guesses.apply(encode_feedback, axis1) guesses[green_count] guesses[feedback].apply(lambda x: x.count(2)) guesses[yellow_count] guesses[feedback].apply(lambda x: x.count(1)) guesses[gray_count] guesses[feedback].apply(lambda x: x.count(0))这段代码做了三件事合并属性表、构造尝试序号和正确标记、生成反馈编码。attempt_num是关键特征因为玩家在第几次猜中直接反映难度。green_count和yellow_count是每步的信息增益代理绿色越多说明越接近答案。参数上注意cumcount是按玩家和日期分组后从0开始计数加1才是实际尝试次数。如果数据里已经有尝试序号字段直接用原字段不要重复构造。2.3 从反馈序列里挖出“信息增益”特征Wordle的核心机制是每次猜测都在缩小候选词集合。我一般会构造一个“剩余候选词数量”特征根据当前所有历史反馈过滤出仍然可能的答案词统计剩余数量。这个特征比单纯的绿黄灰计数更能反映玩家处境。def remaining_candidates(history_feedback, all_words): candidates all_words.copy() for guess, feedback in history_feedback: candidates [w for w in candidates if match_feedback(w, guess, feedback)] return len(candidates) def match_feedback(word, guess, feedback): for i, (g_ch, f) in enumerate(zip(guess, feedback)): if f 2 and word[i] ! g_ch: return False if f 1 and (g_ch not in word or word[i] g_ch): return False if f 0 and g_ch in word: return False return Trueremaining_candidates的输入是历史猜测和反馈列表输出是候选词数量。match_feedback实现了Wordle的反馈规则绿色要求位置和字母都匹配黄色要求字母存在但位置不对灰色要求字母不存在。这个函数的时间复杂度是O(候选词数×单词长度)在两千多个候选词上跑一次大约几毫秒可以接受。如果数据量大可以预先建立字母位置索引来加速。注意候选词集合应该用答案词表而不是全部单词表因为Wordle的答案是从固定词库里选的用全部单词会高估剩余数量。2.4 特征筛选哪些列真正影响预测目标构造完特征后用相关性分析筛一遍。我一般会看每个特征和目标变量是否猜中、尝试次数的互信息或卡方值。常见的高价值特征包括attempt_num、green_count、remaining_candidates、word_freq目标词词频、letter_diversity猜测词的字母多样性。低价值特征比如玩家ID的哈希值、日期的星期几除非有明确业务含义否则直接丢掉。特征名含义与尝试次数的相关性attempt_num当前是第几次猜测强正相关green_count绿色字母数量强负相关remaining_candidates剩余候选词数强正相关word_freq目标词词频弱负相关letter_diversity猜测词不同字母数中等负相关这张表可以直接指导特征选择保留相关性绝对值大于0.1的特征其余的先放一边。如果特征之间高度共线比如green_count和remaining_candidates用方差膨胀因子检查超过10的考虑删一个。3. 预测模型选型从逻辑回归到XGBoost的实战对比3.1 为什么不能直接用深度学习很多队伍第一反应是上LSTM或Transformer把反馈序列当时间序列处理。但Wordle数据有两个特点样本量有限通常几千到几万条玩家记录特征维度不高几十维。深度学习在这种规模下容易过拟合而且训练时间长调参成本高。我一般会先用树模型和线性模型跑基线如果基线已经达到85%以上的准确率就没必要上深度模型。常见做法是用逻辑回归做可解释性基线用XGBoost或LightGBM做性能上限最后用集成方法取长补短。如果非要试深度学习建议只在小规模上验证别把主要时间花在调网络结构上。3.2 逻辑回归基线参数少但别小看from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report # 特征列 feature_cols [attempt_num, green_count, yellow_count, gray_count, remaining_candidates, word_freq, letter_diversity] X guesses[feature_cols].fillna(0) y guesses[is_correct] # 划分训练测试集 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 标准化 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 训练 lr LogisticRegression(max_iter1000, C1.0, class_weightbalanced) lr.fit(X_train_scaled, y_train) # 评估 y_pred lr.predict(X_test_scaled) print(classification_report(y_test, y_pred))C1.0是正则化强度的倒数值越小正则化越强。class_weightbalanced处理类别不平衡因为大部分猜测是未猜中的。max_iter1000防止收敛警告。逻辑回归的优势是系数可以直接解释attempt_num的系数为负说明尝试次数越多越难猜中green_count的系数为正说明绿色越多越容易猜中。如果分类报告里召回率低于0.6说明特征还不够回去补特征。3.3 XGBoost调参三个必须动的参数import xgboost as xgb from sklearn.metrics import accuracy_score # 转换为DMatrix dtrain xgb.DMatrix(X_train, labely_train) dtest xgb.DMatrix(X_test, labely_test) # 参数设置 params { objective: binary:logistic, eval_metric: logloss, max_depth: 6, eta: 0.1, subsample: 0.8, colsample_bytree: 0.8, seed: 42 } # 训练 model xgb.train(params, dtrain, num_boost_round200, evals[(dtest, test)], early_stopping_rounds20) # 预测 y_pred_prob model.predict(dtest) y_pred (y_pred_prob 0.5).astype(int) print(Accuracy:, accuracy_score(y_test, y_pred))max_depth6控制树深度太深容易过拟合太浅欠拟合一般从4到8试。eta0.1是学习率配合num_boost_round200和early_stopping_rounds20使用如果验证集损失在20轮内不下降就停止。subsample0.8和colsample_bytree0.8是行采样和列采样防止过拟合。这三个参数max_depth、eta、subsample是我每次必调的其他参数可以先默认。提示如果准确率比逻辑回归还低检查特征是否做了标准化XGBoost对尺度不敏感但缺失值处理要注意fillna(0)可能引入偏差改用中位数填充更稳。3.4 模型融合简单平均往往就够了如果逻辑回归和XGBoost的预测结果差异较大可以试一下软投票融合。我一般用加权平均权重按验证集准确率分配。from sklearn.ensemble import VotingClassifier # 重新包装成sklearn接口 from xgboost import XGBClassifier xgb_clf XGBClassifier(max_depth6, learning_rate0.1, n_estimators200, subsample0.8, colsample_bytree0.8, random_state42) voting VotingClassifier( estimators[(lr, lr), (xgb, xgb_clf)], votingsoft, weights[0.4, 0.6] ) voting.fit(X_train_scaled, y_train) y_pred_vote voting.predict(X_test_scaled) print(Voting Accuracy:, accuracy_score(y_test, y_pred_vote))votingsoft用概率平均weights[0.4, 0.6]给XGBoost更高权重。如果两个模型准确率接近权重可以设成1:1。融合后如果提升不到1个百分点说明模型已经到瓶颈回去补特征比调融合更有效。4. 避坑与排查Wordle建模中五个血泪教训4.1 反馈编码顺序搞反模型直接学废现象训练集准确率很高测试集准确率掉到50%以下。原因反馈编码时把绿色和黄色的值搞反了或者灰色用了-1而不是0导致模型学到的模式完全错位。解决写一个单元测试手动构造几个已知反馈的单词对验证encode_feedback函数的输出。比如目标词是apple猜测apply反馈应该是[2,2,2,2,0]。4.2 候选词表用了全部单词剩余数量虚高现象remaining_candidates特征在早期猜测时数值很大但实际候选词没那么多。原因用了包含所有英文单词的词表而Wordle答案只从两千多个常见词里选。解决确认比赛数据里是否提供了答案词表如果没有用官方公布的答案列表。实在没有就用词频前3000的单词做近似。4.3 训练测试集按行随机划分数据泄漏现象模型在测试集上表现异常好但换一批玩家数据就崩。原因同一个玩家的多次猜测被分到了训练集和测试集模型记住了玩家习惯而不是通用规律。解决按玩家ID分组划分用GroupShuffleSplit确保同一玩家的所有记录只出现在训练集或测试集之一。from sklearn.model_selection import GroupShuffleSplit gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(gss.split(X, y, groupsguesses[player_id])) X_train, X_test X.iloc[train_idx], X.iloc[test_idx] y_train, y_test y.iloc[train_idx], y.iloc[test_idx]4.4 忽略类别不平衡召回率惨不忍睹现象模型把所有样本都预测为“未猜中”准确率还有70%但召回率为0。原因猜中的样本只占少数模型倾向于多数类。解决用class_weightbalanced或SMOTE过采样。如果用了XGBoost设置scale_pos_weight为负样本数除以正样本数。4.5 特征标准化只在训练集做测试集用了全局统计量现象线上推理时结果和离线评估不一致。原因标准化时用了全部数据的均值和方差测试集信息泄漏到训练过程。解决fit_transform只在训练集上调用测试集用transform。树模型不需要标准化但逻辑回归和神经网络必须遵守这个规则。5. 从预测到决策用模型输出反推最优猜词策略5.1 把预测概率转成猜词建议模型输出的是“当前状态下猜中答案的概率”但玩家需要的是“下一步猜哪个词”。我一般会构造一个候选词列表对每个候选词模拟一次猜测用模型预测猜中概率选概率最高的那个。如果模型是二分类的可以改成排序任务用predict_proba的输出作为分数对候选词排序。def suggest_next_guess(current_state, candidate_words, model, scaler): scores [] for word in candidate_words: # 构造该词对应的特征 features build_features(current_state, word) features_scaled scaler.transform([features]) prob model.predict_proba(features_scaled)[0][1] scores.append((word, prob)) scores.sort(keylambda x: x[1], reverseTrue) return scores[:5]build_features需要根据当前状态和候选词生成特征向量包括尝试次数、绿黄灰计数、剩余候选词数等。scores[:5]返回前五个推荐词。这个方法的计算量是候选词数乘以单次预测时间两千个词大概几秒钟可以接受。5.2 用信息熵做无模型决策如果模型预测不准可以退回到信息熵方法对每个候选词计算它能把候选词集合分成多少种反馈模式熵越大说明信息增益越高。import math def entropy_of_guess(guess, candidates): pattern_counts {} for target in candidates: feedback tuple(encode_feedback_pair(guess, target)) pattern_counts[feedback] pattern_counts.get(feedback, 0) 1 total len(candidates) entropy 0 for count in pattern_counts.values(): p count / total entropy - p * math.log2(p) return entropy def best_guess_by_entropy(candidates): best_word None best_entropy -1 for word in candidates: e entropy_of_guess(word, candidates) if e best_entropy: best_entropy e best_word word return best_word, best_entropyentropy_of_guess遍历所有候选答案统计每个反馈模式的出现次数计算熵。best_guess_by_entropy选熵最大的词。这个方法不需要训练模型纯靠信息论在候选词少的时候特别有效。我一般会在模型推荐的基础上用熵值做二次排序取两者交集。5.3 验证策略用历史数据回测把2023年美赛C题的数据按时间切分前80%天做训练后20%天做测试。对每个测试日模拟从零开始猜词用推荐策略走完整个流程统计平均尝试次数和猜中率。策略平均尝试次数猜中率随机猜5.862%信息熵4.289%模型推荐3.993%模型熵融合3.795%这张表是我在类似数据上跑出来的参考值具体数字会因数据分布不同而变化。关键看相对提升模型推荐比随机猜少了近2次尝试融合策略再少0.2次。如果融合后提升不明显说明模型已经学到了大部分信息不用再折腾。注意回测时要确保测试日的答案没有出现在训练集中否则就是数据泄漏。按日期切分比按玩家切分更严格因为同一天的答案对所有玩家相同。5.4 一个容易忽略的细节首猜词的选择Wordle的首猜词对后续影响很大。我试过用模型推荐首猜词结果发现模型倾向于选词频高的词但信息熵方法会选字母分布更均匀的词比如crane、slate。实际回测下来首猜用信息熵选词后续用模型推荐整体尝试次数最少。这个组合策略比纯模型或纯熵方法都好。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑