ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

美赛C题DWTS第一问复盘:观众投票反推模型的逻辑回归与特征工程实战

美赛C题DWTS第一问复盘:观众投票反推模型的逻辑回归与特征工程实战 26美赛C题DWTS第一问复盘观众投票反推模型的完整思路与代码先说我自己的参赛感受。今年美赛C题拿到“Data With The Stars”这道题的时候第一反应是这场面我熟数据挖掘题给的还是《与星共舞》这种综艺节目数据观众投票、评委打分、淘汰结果全都有。这种题看起来亲切但真正动手之后才发现第一问虽然只是“反推”观众投票与最终结果的关系却把数据处理、特征工程、模型解释和论文叙事全部串了一遍一点不比后几问省心。这篇文章不打算复述题目原文而是把我做第一问时的完整思路、踩过的坑、最后采用的模型和代码一次性整理出来。目标很明确给后面准备美赛或者想练数据挖掘题的读者一套可以直接“抄作业”的框架。不管是还在入门阶段、对逻辑回归和特征构造不熟的同学还是已经有建模经验但想看看别人怎么处理“反推”这个说法的老手这篇文章都值得你花十分钟过一遍。1. 先拆题“观众投票反推模型”到底要反推什么第一眼看这道题很多人会被“反推”两个字卡住。它到底是要用观众投票预测选手是否被淘汰还是要从淘汰结果里倒推出观众投票占多大比重我个人的理解是这两种解读都不完全真正要做的是把“结果”拆解开搞清楚评委打分和观众投票分别以什么权重、什么形式影响了最终排名。1.1 从跳舞节目的规则看数据怎么生成《与星共舞》这类节目的比赛规则并不复杂。每周选手表演三位评委各自给一个技术分再加上观众通过短信、电话、网络等方式投票把评委分和观众票数结合起来决定本周淘汰谁。关键点在于节目组并没有公开宣布这两部分的精确加权方式留给观众的只有每周公布的淘汰结果、选手排名和票数这些外部数据。这个“规则不透明”恰恰是出题人埋的坑也是第一问的价值所在。如果我们能拿到足够多的历史周次数据把每位选手在每一周的评委得分、观众票数、排名档位甚至淘汰与否全部对齐就可以建立一个“结果反推输入”的模型。换句话说先从外部表现倒推出隐藏的加权公式再正向预测未来一周谁最危险。我当时查了一下DWTS历年的数据格式通常包含参赛选手名称、周次、舞种、评委给分三个或四个维度、观众投票占比有时是排名区间有时是具体百分比、当周排名、是否被淘汰等字段。数据集中缺失值不少尤其是观众票数不同赛季的统计口径还不一样有的给的是票数绝对值有的给的是占比有的干脆是“安全区”和“危险区”两类。这些数据上的坑后面会单独展开。1.2 第一问的“反推”本质是权重还原把“反推”落到模型上我的处理方式是把它变成一个回归或者分类问题。分类版本的落地方式是用选手当周获得的所有信息预测他是否进入危险区/是否被淘汰回归版本的落地方式是用这些信息预测当周最终得分的连续值。但无论选哪种真正要看的都不是预测精度而是模型给每个特征分配的系数或重要性。为什么系数这么关键因为通过系数我们就能还原出节目组没有公开的加权关系。举个例子如果逻辑回归里“观众投票占比”这个特征的标准化系数绝对值比“评委平均分”大一倍那说明观众投票对淘汰结果的影响大约是评委打分影响的两倍。这就是题目里“反推”两个字想让你做的事情用可观测的结果和数据倒推出隐藏规则中的权重结构。这里有一个很多队伍容易踩的误区一上来就上一个复杂的集成模型随机森林、XGBoost全都上了最后预测准确率确实不错但一问“那观众投票到底比评委重要多少”就答不上来了。美赛不是算法竞赛传统机器学习算法在这里的作用只是辅助验证真正的核心能力是把模型结果翻译成业务语言告诉读者“这道题里每个因素具体是怎么起作用的”。1.3 模型选型怎么定我的选型逻辑是这样的先做可解释性最强的模型用线性回归和逻辑回归探路拿到系数和显著性再用决策树和随机森林做稳健性检查确认线性模型发现的规律在非线性模型里也存在最后用可视化把“权重随时间变化”“权重随赛段变化”这些动态信息呈现出来。三个步骤层层递进论文里既有表格也有图既有系数也有严谨性验证评委能看到一条完整的故事线。写代码的时候还要明确一点这套题的数据往往不是独立同分布的因为同一个选手的多周记录彼此高度相关。简单套sklearn的默认交叉验证会在论文答辩时被问住。更稳的做法是在选手维度做分组保证训练集和验证集里没有同一个人的不同周次互相泄漏。我最后用的是GroupKFold后面代码部分会给出完整写法。2. 数据准备不是所有列都能直接丢进模型每次带队伍打数据题我都会先花两三个小时做数据勘察而不是急着写建模代码。题目给的数据集如果你连每一列的真实含义、取值范围、缺失比例都没弄清楚后续建模就是在沙地上盖楼。第一问里观众投票数据虽然看起来是核心但它恰恰是脏数据重灾区。2.1 数据里有哪几类信息我拿到整理后的数据后先把字段分成了四类身份类选手姓名、赛季编号、周次这类字段主要用来做分组和排序一般不进特征矩阵评委信息类三位评委的打分、总分、平均分这类字段是连续值比较规整缺失情况不多观众反馈类观众投票占比、票数、排名区间、是否进入倒数区这类字段是反推的核心但口径杂、缺失多结构性信息舞种、主题周、是否是团队赛、是否之前已经进过危险区这类字段决定了同一周内选手之间能不能横向比较。理解这些信息的层次之后就能明白为什么不能把所有列直接堆给模型。比如舞种是文本型类别特征不能直接和评委分、投票占比放在一个尺度里比如周次是顺序型变量但对线性模型来说直接放进“周次”数值相当于默认了“第六周和第七周的影响差异等于第二周和第三周的差异”这个假设并不一定成立。2.2 缺失值和脏数据的处理观众投票相关字段的缺失值处理我花的时间远超预期。有的周次直接缺了投票占比但保留了“是否进入倒数区”有的赛季所有选手都有票数但没有占比还有些记录里评委总分明明很低选手却很安全明显说明该赛季观众投票权重特别高如果强行用全数据的均值填补就会抹掉这种赛季差异。我最后采用的思路是先做三档缺失处理如果某列缺失比例超过30%或者只在少数几个赛季出现就不直接作为特征使用而是转化为一个“是否有该数据”的辅助标记列如果缺失比例在10%到30%之间并且已有排名或安全状态可以用同赛季、同周次的其他选手中位数进行填补如果缺失比例低于10%直接删除整条记录因为保留样本量比填补引入的偏差更重要。这里要特别说一句填补观众投票这种行为本身隐含一个强假设缺失机制与结果无关。但从实际数据来看投票数据缺失的周次往往是节目组突然改规则或数据集作者漏抓的周次并不随机。所以我在论文里明确写了一句“缺失数据的处理方式对参数估计产生了影响我们通过敏感性分析验证了核心结论在不同填补策略下的稳定性”这句话在答辩时能挡住很多问题。2.3 特征工程的几个关键构造第一问的特征工程不在于多而在于准。我只做了四个方向的扩展第一个是标准化。评委打分通常在一个较小范围内而票数可能是数万、数十万的量级不统一尺度的话逻辑回归的系数会被拉得很歪迭代收敛也慢。我使用StandardScaler做标准化处理同时保存好scaler用于后续解释。第二个是“相对表现”特征。绝对分和绝对票数在不同周次之间不可比因为选手数量在变、观众基数在变。所以我把每个特征先按照“当周所有选手中的百分位排名”进行转换得到一个0到1的相对值。比如某个选手评委平均分是8.5当周最高是9.2那它的百分位排名可能是0.78。这个相对值比原始分更能代表竞争力。第三个是滞后特征。上一周是否进入危险区、上周排名变化方向这些时序信息比当周的单点信息更重要。一个连续两周垫底的选手即使本周评委分提高了一点也仍然大概率危险。我构造了“上一周待淘汰状态”“过去两周平均排名”两个滞后列。第四个是交互特征。评委打分和观众投票可能会互相补偿比如评委给高分但观众投票低或者评委给低分但人气高。简单地把两个特征放进模型会让模型默认它们的影响是固定的、可叠加的而现实很可能不是这样。我构造了一个“评委平均分 × 观众投票占比”的交互项用来捕捉这种补偿效应。写到这里你可能会发现特征工程的重点并不是让模型的预测精度变高而是让之后“反推权重”这个动作更加可信。如果你的特征本身就没构造好模型输出的系数是没有解释意义的。3. 反推模型的核心代码实现下面进入正题代码部分。我采用的建模策略是先用逻辑回归做“是否被淘汰”的二分类预测把输出系数当作第一问的核心答案再用随机森林配合分组交叉验证验证结论的稳定性。整套代码跑完只需要几分钟但它覆盖了从特征处理到模型解释的全部流程。3.1 目标变量怎么定义题目里面给出的“观众投票反推”不一定直接给“是否淘汰”这个标签更多时候是给“是否进入危险区”或“排名是否下降”。所以第一步是构造一个清晰且稳定的目标变量。我的做法是综合多源信息生成一个二分类标签如果数据中该选手该周次标记为“淘汰”或者排名落入倒数15%且次周不再出现就记作1否则记作0。import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.model_selection import GroupKFold, cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score, accuracy_score df pd.read_csv(dwts_episode_data_cleaned.csv) # 构造目标变量 df[eliminated] ((df[status] Eliminated) | (df[rank_pct] 0.15) df[next_week_appear].fillna(0).eq(0)).astype(int) # 基础特征列 feature_cols [ judge_avg_score, # 评委平均分 vote_share, # 观众投票占比 score_percentile, # 当周评委得分百分位 vote_percentile, # 当周投票百分位 prev_eliminated, # 上周是否危险 avg_rank_last2, # 过去两周平均排名 interaction_score_vote # 交互项 ]定义目标变量的时候我踩过一个坑。最开始我只用“status”字段判断是否被淘汰但后来发现很多周次里选手并没有明确标注状态而是直接消失在下周名单里。如果忽略这种情况正样本会严重偏少模型会认为所有人都是安全的。所以在构造标签时我加上了“次周不再出现”这个条件捕捉隐性淘汰样本。这一步直接让正样本数量增加了大约四分之一效果非常明显。3.2 回归系数才是“反推”的关键这一步是整个第一问的核心。我使用逻辑回归训练模型然后输出标准化后的回归系数。注意系数必须基于标准化后的特征才有意义否则“票数百万级”和“评分个位数”两个系数的绝对值没法直接比较。# 标准化数值型特征 scaler StandardScaler() scaled_features scaler.fit_transform(df[feature_cols]) X pd.DataFrame(scaled_features, columnsfeature_cols) y df[eliminated].values # 分组交叉验证同一选手的所有周次只进同一边 groups df[contestant_id].values gkf GroupKFold(n_splits5) # 逻辑回归 logreg LogisticRegression(C1.0, max_iter1000, random_state42) cv_scores cross_val_score(logreg, X, y, cvgkf, scoringroc_auc, groupsgroups) print(Logistic Regression GroupKFold AUC: {:.4f} ± {:.4f}.format(cv_scores.mean(), cv_scores.std())) # 在全量数据上重新拟合输出系数 logreg.fit(X, y) coef_df pd.DataFrame({ feature: feature_cols, coef: logreg.coef_[0], abs_coef: np.abs(logreg.coef_[0]) }).sort_values(abs_coef, ascendingFalse) print(coef_df)AUC是一个相对稳健的衡量指标我当时跑出来的结果是0.82到0.90左右。你能看到这个分数并不算极高但这不是重点重点是系数表格。表格中“vote_percentile”的系数如果显著高于“score_percentile”就能直接回答“观众投票比评委打分更能决定淘汰风险”这一结论。这里有一个细节我必须提醒使用GroupKFold而不是普通KFold是因为同一个选手的多周记录高度相关。如果普通交叉验证不小心把同一个选手的第1周放训练集、第2周放验证集模型就会通过“这个人上周怎么样了”的信息作弊导致AUC虚高。很多论文里的高分模型最后被评委质疑根因就是交叉验证的分组策略不严谨。3.3 随机森林的交叉验证与鲁棒性检查逻辑回归虽然可解释性强但它对特征之间非线性关系的表达能力有限。为了确保结论不是因为模型过于简单而出现偏差我额外训练了一个随机森林分类器同样用GroupKFold交叉验证评估性能并输出特征重要性。rf RandomForestClassifier( n_estimators300, max_depth5, min_samples_leaf5, random_state42 ) rf_cv_scores cross_val_score(rf, X, y, cvgkf, scoringroc_auc, groupsgroups) print(Random Forest GroupKFold AUC: {:.4f} ± {:.4f}.format(rf_cv_scores.mean(), rf_cv_scores.std())) rf.fit(X, y) importance_df pd.DataFrame({ feature: feature_cols, importance: rf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance_df)随机森林的特征重要性和逻辑回归的系数排序如果高度一致说明结论是稳健的如果差异很大就说明特征之间存在非线性交互需要进一步分析。我跑出来的结果显示“vote_percentile”在两种模型中都稳居前二“score_percentile”紧随其后交互项的重要性也不低。需要强调的是随机森林的输出只能告诉你“观众投票占比很重要”但不能告诉你“它到底有多重要、方向是正还是负”。所以随机森林在这里是作为稳健性检查而不是核心答案。美赛论文里这种“双模型交叉验证”的思路非常讨喜因为评委可以清楚地看到你的结论不止依赖于某一个模型。4. 结果解读观众投票对淘汰结果的影响力到底有多大模型跑完之后最激动人心的部分就是把系数翻译成一句能让评委看懂的结论。这部分不是单纯画几张图就完事而是要结合回归系数的数值、随机森林的特征重要性和分周次的趋势变化给出一个逻辑严密、有数据支撑的判断。4.1 从系数看投票权重先看标准化逻辑回归系数。假设最终输出如下数值仅为示意实际以跑出来的结果为准vote_percentile1.85score_percentile0.92prev_eliminated1.10interaction_score_vote0.45avg_rank_last20.35judge_avg_score0.20vote_share0.15这个系数表的直接解读是在其他条件相同的情况下观众投票百分位每上升一个标准差选手被淘汰的log-odds下降1.85同时评委得分百分位每上升一个标准差log-odds下降0.92。换算成更为直观的odds ratio观众投票的影响大约是评委打分影响的2倍左右具体数值是exp(1.85)/exp(0.92)≈2.5。这就是“反推”得来的核心答案观众投票在决定选手去留时权重约为评委打分权重的2.5倍。有一点要注意不要直接说“观众投票权重是评委的2.5倍”因为模型用的是标准化系数比较的是“一个标准差的变动”造成的影响不是“原始一分的变动”造成的影响。为了让结论更严谨我在论文里加了这样一句基于标准化特征系数比观众投票的相对重要性约为评委打分的2.2至2.8倍这一区间在不同随机种子和不同模型设置下保持稳定。4.2 按周次拆解的曲线DWTS这类节目有一个特殊的规律随着赛程推进选手数量逐渐减少留下的都是高人气、高水平的选手评委分和观众投票的相关性会发生变化。为了捕捉这种动态我按照周次把数据切片分别训练逻辑回归模型记录每周“vote_percentile”和“score_percentile”的系数变化。week_coefs [] for week in sorted(df[week].unique()): sub df[df[week] week] if len(sub) 20: continue X_sub scaler.fit_transform(sub[feature_cols]) y_sub sub[eliminated].values model LogisticRegression(max_iter500, random_state42) model.fit(X_sub, y_sub) coef_dict dict(zip(feature_cols, model.coef_[0])) coef_dict[week] week week_coefs.append(coef_dict) week_df pd.DataFrame(week_coefs) import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(10, 5)) sns.lineplot(dataweek_df, xweek, yvote_percentile, labelvote_percentile coef) sns.lineplot(dataweek_df, xweek, yscore_percentile, labelscore_percentile coef) plt.axhline(0, colorblack, linewidth0.5) plt.title(Coefficient Trends by Week) plt.legend() plt.tight_layout() plt.savefig(coef_trends_by_week.png, dpi150)画出来的曲线通常能发现一个有意思的现象前几周观众投票系数还不算特别夸张评分的影响力相对更大到赛程中后段观众投票的系数开始明显上翘评委打分的系数则逐渐平稳甚至下降。这个趋势和节目的实际观感是吻合的越到后期留下的人实力都差不多评委分拉不开差距人气的占比就越来越大。这一小节的发现既是在回答“反推”这个核心问题又是在为第二问、第三问提供依据。如果后面问你“如何预测下一位淘汰的选手”你完全可以把“后程观众投票权重加大”这个规律作为预测逻辑的一部分。4.3 可解释的模型才是论文的加分项关于结果解读我还想多说两句。美赛的评委在阅读论文时最怕看到的就是“模型AUC 0.95所以模型很好”这种没有任何业务解释的表述。他们真正关心的不是你的准确率有多高而是你是否能讲清楚数据背后的机制。所以我在论文里做了三件事来强化可解释性。第一把逻辑回归系数换算成odds ratio给每一位选手的“观众投票百分位每提升0.1淘汰风险降低多少百分比”这种直白的描述。第二用SHAP值辅助分析。虽然SHAP用在逻辑回归上有点大材小用但它能画出每个特征对每个样本的影响分布让评委一眼看到是否存在某个选手被“人气”因素显著影响。第三做了敏感性分析。把缺失值填补策略换两三种重新跑系数确认核心结论“vote_percentile系数始终最高”不受填补策略影响。这三件事不需要花太多时间但在论文中占的篇幅和印象分很高。数据挖掘类题目最后拼的不是谁的模型花哨而是谁对数据的理解更深、对结论的表达更清楚。5. 踩坑记录与排查思路每一场比赛都是在踩坑和填坑中度过的。这次第一问里遇到的大大小小问题我把特别有代表性的几个整理出来做成一个速查表。如果你在建模的时候发现结果异常先对照这个表排查一遍。5.1 常见问题速查表现象可能原因排查思路AUC非常高0.98以上目标泄漏检查是否把“次周是否出现”或“最终排名”等未来信息当成了特征逻辑回归系数正负方向不符合直觉特征尺度未统一检查是否做了标准化或特征之间存在严重多重共线性随机森林特征重要性与逻辑回归系数排序差异巨大特征交互或单调性假设不成立增加交互项或改用可解释性工具分析非线性关系不同赛季的模型系数差异极大赛季间规则或机制不同按赛季分组进行分析或把赛季id作为分层变量交叉验证得分极不稳定选手分组未隔离改用GroupKFold禁止同一选手跨训练集/验证集缺失值填补后结论反转缺失机制不随机做敏感性分析对比不同填补策略的结果第一个“目标泄漏”是最危险的坑。有一次我把“next_week_appear”字段当作特征放进来了模型AUC直接飙到0.98差点以为自己的模型无敌了。后来仔细检查才发现这个字段本身就是从“下一周是否还出现在名单里”构造出来的而淘汰结果和“下一周是否出现”几乎是一回事这显然属于用未来信息预测现在属于典型的泄漏。处理办法很简单把所有和未来周次相关的字段全部剔除只保留当周以及过去的信息。第二个坑是多重共线性。我同时保留了judge_avg_score和score_percentile这两个特征本质上是线性相关的因为后者就是从前者排名得到的。如果一个模型里同时出现两个高度相关的特征逻辑回归的系数会被稀释甚至出现方向相反的情况。这是我后来在系数表里剔除“judge_avg_score”、保留“score_percentile”的原因也是为什么特征千万不要贪多够用就好。5.2 时间分配和论文配合的建议最后说一下时间分配。第一问虽然是全题的第一步但我建议不要在第一问上追求过高的模型精度而要把时间花在“讲清楚反推逻辑”这件事上。我当时的节奏是这样的拿到数据后先做30分钟的数据勘察然后花1.5小时做特征工程和目标变量定义再花1小时训练逻辑回归和随机森林模型与此同时让负责论文的队友开始搭论文框架把数据描述、变量说明、建模流程图这些固定内容先写出来。等模型训练结束立刻把系数表和图表交给论文组由他们撰写分析段落。这样第一问从构思到成稿大约用掉6到7个小时留出足够的时间处理后面的复杂问题。写代码时还有一点经验可以分享把所有随机种子固定住包括numpy、sklearn、pandas的随机采样这样才能保证每次跑出来的结果一致论文里的数字和代码复现的数字对得上。比赛后期被评委质疑“数字来源”是非常致命的固定随机种子这件事虽然不起眼但没有它你会多出很多不必要的麻烦。6. 一点额外的心得第一问做到最后我最大的感受是美赛的数据挖掘题模型本身从来不是难点难点在于你能不能把一个模糊的业务问题翻译成清晰的数据问题再把它讲回成一个有说服力的业务结论。观众投票反推模型的本质并不是预测谁会被淘汰而是通过淘汰结果量化“人气”和“实力”的博弈关系这个结论能带给节目组、赞助商甚至观众一些有价值的参考依据。代码实现方面如果你是从零开始的小白把上面这些代码理解透、亲手跑一遍基本就能掌握美赛C题第一问的完整套路。如果你已经有经验那我更希望你能关注我在特征构造、目标变量定义和分组交叉验证这几个细节上的处理方式这几个细节决定了一篇论文是“看起来做了很多”还是“真的想明白了”。后续如果有时间我可能会继续写第二问和第三问的思路复盘比如如何基于第一问的权重结果预测下周淘汰名单或者如何设计一个基于模拟的赛制优化方案。到时候再和大家细聊。
返回列表