返回列表

#1 solution - generalization with linear regression

559. GoDaddy - Microbusiness Density Forecasting | godaddy-microbusiness-density-forecasting

开始: 2022-12-16 结束: 2023-06-16 销量与需求预测 数据算法赛
第一名解决方案 - 线性回归的泛化方法

第一名解决方案 - 线性回归的泛化方法

作者:@kaggleqrdl
发布日期:2023-03-16
投票数:48
比赛排名:第1名

最新:我已上传获得奖金分配所需的正式模型提交文件:https://www.kaggle.com/competitions/godaddy-microbusiness-density-forecasting/discussion/425000

代码地址 - https://www.kaggleqrdl/first-place-code,下方有详细解释。

致谢

首先我要感谢Kaggle、Venture Forward团队和GoDaddy。不仅感谢他们创建了Kaggle平台、主办这场比赛并提供小微企业数据——这些 truly 是令人难以置信的信念之举——还要感谢他们对我坚持透明度的耐心。我是一名彻底的平均主义者,坚信透明度是我们实现这一目标的途径。

引言

本次比赛的目标是预测2023年3月、4月、5月(分别提前3、4、5个月)美国3135个县的小微企业密度。这里的小微企业密度大致等于GoDaddy注册域名数量除以该县成年人口(以美国人口普查2年滞后数据为准)。

例如:如果2023年2月,密苏里州德卡尔布共有10个GoDaddy域名注册,根据2021年人口普查该县成年人口为200人,那么该月该县的小微企业密度将增加10/200,即0.05。

我们获得了2019年8月至2022年12月的历史小微企业数据进行训练,以及一个用于验证1月数据的"公开排行榜"数据。Kaggle提供了公开排行榜的得分,我们每天可以提交5次结果。

方法

为了赢得比赛,我基于过度拟合1月公开排行榜的策略建立了基线预测,并使用线性回归将其预测扩展到3月、4月和5月。

'sklearn.linear_model.LinearRegression',  ['pct_college_2021', 'l11_uemp', 'shiftf1_l1_active']  - 4个月提前预测
'sklearn.linear_model.LinearRegression',  ['shiftf1_l1_active_logit', 'pct_college_2019', 'l4_active','l10_lf'] - 3个月提前预测
'sklearn.linear_model.LinearRegression',  ['l8_uemp', 'l1_ur', 'l11_uemp', 'l10_ur', 'pct_bb_2017'] - 2个月提前预测
  1. l# 表示滞后月份数,shift是实际值,l#_ 表示百分比变化
  2. shiftf1_l1_active_logit 表示上月活跃用户是否有变化(l1_active != 1)
  3. pct_college_2021 / pct_bb_2017 来自主办方分享的普查数据
  4. uemp / lf / ur 来自我分享的数据集

我通过在最后一个可能的窗口(分别为2、3、4个月)进行交叉验证且不重叠的方式选择了这些特征。

关键洞察

似乎有帮助的核心思想:

  • 排行榜过度拟合/探测。我通过使用排名最高的公开排行榜notebook,并重点关注数据集中近期波动显著的约10个县来实现这一点。之所以这样做,是因为在非稳态时间序列数据中,最后一个值是最强大且最具说服力的信号。值得注意的是,由于12月/1月数据问题(我们获得的数据有60%不正确,按SMAPE衡量),这项技术受到严重影响,我在公开排行榜上领先前20名的优势从约0.2降至约0.08。当排行榜数据有误时,过度拟合排行榜的效果会大打折扣。
    关于此方法的更多解释 - https://www.kaggle.com/competitions/godaddy-microbusiness-density-forecasting/discussion/413109。我也在比赛早期这里这里解释过这些想法。notebook中包含用于SMAPE求解和探测技术的代码。
  • 线性回归。我尝试了几乎所有其他(*)模型和技巧,甚至深度学习/神经网络。线性回归始终能产生更可靠的CV分数。在时间序列数据中,过度拟合始终是一个担忧,调整其他模型并说服自己找到了更优方案非常容易。
  • 贪心特征选择的早停。这主要是基于直觉完成的,但在进行了数千次实验后,我对这一点有了很好的把握。我注意到当特征超过4或5个时就会发生过拟合,此外改善率的下降也是过拟合的另一个信号。某些特征似乎比其他特征具有更多信号(例如L10/11就业、宽带普查数据),因此我会继续添加直到它们进入列表,但不会添加太多。
  • 最后窗口CV用于特征选择。我的假设是,作为非稳态时间序列,相关的预测特征会发生变化,最好使用最新的CV窗口(2022年最后几个月)。这与我在实验中看到的结果一致,但我要说的是,误差项的标准差相当高,即使均值优于更长的CV周期。风险在于如此短的CV时间使其有点赌博的意味。
  • 尽可能使用更多原始训练数据。通过数据充分性实现泛化是本次比赛的反复出现的主题,任何不重视这一点的技术都会相应降级。我使用了滞后窗口之前的所有数据。我没有修改任何数据(除了移除3个有问题的县),甚至保留了2021年的数据断点。我尝试了各种方法来清理该数据,但它们似乎只会降低结果。
  • 基于活跃用户/人口的取整。这当然是必须的。我发现截断最终并没有帮助。

*sklearn中的每个回归模型、各种arima库(我在这里花了很多时间,感觉是浪费的)、分层模型、xgb、catboost、lgbm、pytorch/dll。我不仅手动调整了上述模型,还尝试了各种自动方法,如网格搜索/optuna。有很多非常令人兴奋的结果,但最终我意识到自己可能只是在过拟合。

值得注意的是,我从公开排行榜到私人排行榜对第2和第3名的领先优势显著增加。我认为这是由于我对4月/5月的预测更优,因为我的3月结果有些差强人意。我对前20名的领先优势也有所增加,尽管不太清楚公开排行榜对他们的模型影响有多大。

不太重要的想法

我也尝试了许多不同的数据集(包括我公开分享的所有数据),甚至包括"godaddy"的谷歌关键词趋势。上述方法结合早停效果最佳。使用其他特征时偶尔会出现良好的CV分数,但添加它们经常感觉像过拟合。

我 briefly 研究过使用zone文件和whois数据库,但这些并非公开可用,所以我当然没有使用它们。

从理论上讲,爬取/抓取会是公开数据,但我已承诺自己不使用任何未公开分享的数据,因此我认为这并不值得付出所需努力。

我使用的所有数据都分享在我比赛开始时制作的数据集中。

我在各县单独模型方面做了很多初步工作,最终认为这是浪费的。几乎没有足够数据来验证上述模型和特征。或许有办法对各县进行分离,问题是——你是否也必须分离训练数据?增加的复杂性是否值得可能只是渐进式的改进?很难说,特别是当你的CV分数如此不稳定时,观察渐进式改进几乎是不可能的。

未来方向

我花时间研究过各县之间的相关性。我认为可以论证不同的县可能以潜在的滞后时间遵循相似的趋势。这里可能还有更多可挖掘的,但需要全局性地完成,而不是单独进行,且你的模型需要适当的数据充分性。

另一个我没有仔细研究的特征是GoDaddy域名的1/2/3年等年度续费,这是最近在与一些域名转售商在域名转售社区论坛聊天时获得的想法。我怀疑他们会提供更多围绕此可加以利用的想法。

最终思考

正如我上面提到的,其他人也提到过,数据是非稳态的,除了一些全局线性增长系数。也有人说较小的县增加了很多噪音,但我认为是正确的,试图看看我们能否在那里做些什么。农村地区周围的统计数据和分析存在缺乏,紧急问题很难被发现。

看待数据的另一种方式可能是波动性而非方向。活跃域名的剧烈波动可能只是由于数据错误,但也可能是由于潜在的社会和经济因素。在后一种情况下,我们需要考虑:波动性是否可能是一种机会,像火花的首次迸发?这些县能否从更有针对性和适时的投资中受益?如果波动性县具有更大潜力,那么波动性的触发因素是什么?这些是值得考虑的问题和实验(信号+干预)。

如果VF团队想联系我(笑),欢迎随时联系。你可能已经注意到,我可以整天和同样感兴趣的人谈论这些东西。

..

团队更新中存在一个bug,我无法更改我原本指向下方消息的排行榜指针,所以你们只能同时看到两个。我原本更感兴趣其他人做了什么,因为我对上述简单方法并不太乐观。回想起来,我本应该更相信我的排除过程。

之前的帖子标题:你对3月/4月/5月相对12月的平均活跃变化是多少?

编辑 - 首先明确说明,你可以通过执行此代码分享你的提交结果。它使用小微企业密度而非活跃用户,但这应该适合比较。

dfc = pd.read_csv("submission.csv")
rt = pd.read_csv("revealed_test.csv").set_index("row_id")
dfc['first_day_of_month'] = dfc['row_id'].apply(lambda x:x[-10:])
dfc['cfips'] = dfc['row_id'].apply(lambda x:int(x[:x.index("_")]))
dfc = dfc.reset_index().set_index("row_id")
dfc['mbd_chg'] = dfc.apply(lambda r:(r['microbusiness_density'] - rt.loc[f"{r['cfips']}_2022-12-01", 'microbusiness_density'])/rt.loc[f"{r['cfips']}_2022-12-01", 'microbusiness_density'], axis = 1)
display(dfc.groupby("first_day_of_month").mean()['mbd_chg'])

....

我们还需要一段时间才能获得最终结果,我认为5月的结果可能在决定最终获胜者方面发挥重要作用。在等待期间,分享我们实际预测的内容可能会很有趣。我们可以比较实际结果,这很好,我已将我的两个结果上传 - 但对于那些不想下载文件的人来说,或许更简单的方法是分享相对活跃用户的平均百分比变化。

这是我使用的代码。它需要你的数据框中12月的准确值,并且需要计算'active'作为其中一列。

如果你不想使用活跃用户,也可以使用小微企业密度,但我发现由于人口变动,它不那么有趣。人口变动中存在一些异常值,使得更难确保不会出错,所以我喜欢有活跃用户可用。下面我同时分享了两种方法。

你可以使用此代码计算cfips/首日:

dfc['first_day_of_month'] = dfc['row_id'].apply(lambda x:x[-10:])
dfc['cfips'] = dfc['row_id'].apply(lambda x:int(x[:x.index("_")]))

使用此代码计算active_chg值:

dfc = dfc.reset_index().set_index("row_id")
dfc['active_chg'] = dfc.apply(lambda r:(r['active'] - dfc.loc[f"{r['cfips']}_2022-12-01", 'active'])/dfc.loc[f"{r['cfips']}_2022-12-01", 'active'], axis = 1)
dfc.groupby("first_day_of_month").mean()['active_chg']

这是我的季节性模型

first_day_of_month
2022-11-01   -0.00635502 
2022-12-01    0.00000000
2023-01-01    0.00328686
2023-02-01    0.00328686
2023-03-01    0.01505438
2023-04-01    0.02265933
2023-05-01    0.02476927
Name: active_chg, dtype: float64

这是我的长期趋势模型

first_day_of_month
2022-11-01   -0.00635502
2022-12-01    0.00000000
2023-01-01    0.00328686
2023-02-01    0.00328686
2023-03-01    0.00881651
2023-04-01    0.01215006
2023-05-01    0.01580625
Name: active_chg, dtype: float64

如果你没有活跃用户列,使用mbd代码:

dfc = dfc.reset_index().set_index("row_id")
dfc['mbd_chg'] = dfc.apply(lambda r:(r['microbusiness_density'] - dfc.loc[f"{r['cfips']}_2022-12-01", 'microbusiness_density'])/dfc.loc[f"{r['cfips']}_2022-12-01", 'microbusiness_density'], axis = 1)
dfc.groupby("first_day_of_month").mean()['mbd_chg']

季节性提交

first_day_of_month
2022-11-01   -0.00635502
2022-12-01    0.00000000
2023-01-01    0.01208731
2023-02-01    0.01208731
2023-03-01    0.02388208
2023-04-01    0.03161783
2023-05-01    0.03375395
Name: mbd_chg, dtype: float64

长期趋势提交

first_day_of_month
2022-11-01   -0.00635502
2022-12-01    0.00000000
2023-01-01    0.01208731
2023-02-01    0.01208731
2023-03-01    0.01764007
2023-04-01    0.02099663
2023-05-01    0.02467693
Name: mbd_chg, dtype: float64

注意:对于2月,我直接使用了1月的数值。分享1月数据当然不是那么有帮助,我只是在这里为了好玩而已。

同比赛其他方案