2026/10/19 5:40:55

民宿价格预测Baseline:从RMSE到特征工程的完整实践

民宿价格预测Baseline:从RMSE到特征工程的完整实践 先聊点实在的。民宿价格预测这个题在数据竞赛和实际业务里都特别常见。很多人一上来就想着堆模型、调参结果做出来的baseline连及格线都摸不到。这个赛道真正卡人的地方根本不在于模型选得多花哨——而是从业务理解到特征构造这一整条链路每一步有没有踩实。我这次的模拟项目X目标很清晰给定某民宿平台的历史订单与房源属性数据要求预测每个房源的每晚价格。这是个典型的回归问题不是分类而且价格数据的分布极不友好长尾严重。做这个baseline我的原则就一条先把评估指标吃透再用最简单的模型跑通全流程最后在此基础上做有针对性的迭代。这篇就把整个思路、关键代码逻辑和那些容易踩坑的细节一次性讲清楚。1. 问题定义与评估指标先把“怎么算赢”搞清楚很多初学者拿到这类题目第一反应是打开Notebook直接跑模型。这是最大的误区。做预测类任务尤其是有明确业务背景的第一步必须是倒推——先看评估指标是什么再看数据给到什么程度最后才轮到建模。1.1 为什么RMSE比MAE更适合价格预测在民宿价格预测这个场景里最常见的评估指标是RMSE均方根误差和MAE平均绝对误差。如果你去看一些公开竞赛会发现价格预测类赛题默认指标往往是RMSE。原因很直接RMSE对误差做了平方相当于给大误差加了更高的惩罚权重。价格预测里什么情况会产生大误差比如某套房源真实价格是1500元/晚模型预测成了800元这个错误在业务上远比“真实价格300元、预测320元”严重得多。RMSE能放大这种不可接受的偏差倒逼模型去学习那些高价位房源的规律而不是只盯着走量的低价房源。MAE的问题在于它太“平均主义”了。假设100套房源里98套是300元上下的小单间2套是3000元的海景别墅MAE会倾向于把精力全部放在那98套上因为把300元预测错30块钱和把3000元预测错300块钱对MAE的贡献是一样的。但在RMSE的视角下后者产生的误差是前者的10倍模型必须花更多力气去拟合高价房。我这次做baseline直接锚定RMSE。如果你拿到的是分类任务那逻辑同理——先搞清楚用Accuracy还是AUC用F1还是召回率再动手不然你后面做的所有特征都可能跑偏。1.2 log变换价格预测的隐藏前置步骤价格数据的分布通常极度右偏。什么意思就是你画个直方图会发现一大片房源集中在200-500元/晚但尾巴拖到几千甚至上万。这种分布对回归模型非常不友好因为模型在训练时看到的大多是低价样本很容易忽略高价房源的规律。行业里常规的做法是对目标变量做对数变换也就是让模型去预测log(价格)而不是价格本身。我实测下来这一步基本是必做的原因有两个第一log变换能把右偏的尖峰分布拉成接近正态分布模型拟合起来更平稳。第二预测的时候你在log空间里做回归评估指标也是在真实价格上算RMSE所以预测完再用np.expm1()还原回来就行。有人会犹豫用RMSE评估但训练目标变成log(价格)这算不算指标和训练目标不一致本质上不冲突。log变换只是让模型更容易学到规律最终还原后RMSE依然会降低。我实测裸训练价格RMSE大概在85-100log变换后普遍能压到70-80这个提升比换任何模型都明显。如果你是入门阶段记住这一条就够了拿到价格类回归任务先做log变换再谈建模。2. 数据探查与预处理先把家底盘清楚有了评估指标的锚定接下来就是常规操作——数据探查。但这里我要多说一句数据探查不是为了画几张漂亮的图发朋友圈而是为了让后续特征工程有方向。你带着问题去看数据和处理完才发现结构不对效率是两回事。2.1 字段类型决定了你能做什么特征民宿价格预测的数据集里常见的字段大概分这么几类数值型卧室数量、卫生间数量、最大容客量、历史评论数、评分等类别型房源类型整套/单间/合住、城市、街区、房东是否超赞房东、是否即时预订文本型房源标题、房源描述、设施列表时间型发布日期、最近评论时间、日历数据我的习惯是先把每个字段的类型、缺失率、唯一值数量跑一遍做初步分类。这里有个经验类别型特征的唯一值数量决定了后续是否值得做目标编码或频次编码。比如城市这个字段如果是上百个城市直接one-hot会把特征维度炸开花LightGBM虽然能处理类别型但高基数类别型特征照样会让模型过拟合。备选思路是先用频次编码看看房价和经验分布是否和频次成趋势再决定要不要升级到目标编码。2.2 缺失值与异常值有些“脏数据”本身就是信号价格预测数据集里的缺失值处理方式不能一刀切。比如“卧室数量”缺失你完全可以用中位数填充因为这对模型来说是噪声很小的信息。但“房东回复率”这种字段缺失含义就很复杂——可能是房东新入驻、还没积累数据也可能是根本不太搭理客户。直接填充中位数会抹掉这种业务信号。我的baseline阶段处理原则是缺失率低于1%的数值字段直接中位数填充缺失率5%-20%的字段新增一个“是否缺失”的0/1标志位再用中位数填充缺失率超40%的字段先试探性放入模型如果LightGBM的特征重要性排名垫底再删除异常值这边我踩过一个真实的坑。某个模拟数据集里有一条房源价格标成99999美元/晚大概率是录入错误或者营销噱头。这种样本如果直接留在训练集里log变换后依然是极端离群点会让模型为了拟合这一个点而扭曲整体规律。关键阈值是看价格分布的分位数——超过99.9%分位的样本我建议直接剔除而不是靠肉眼判断。还有一个很多人忽略的点重复房源ID。同一条房源在数据里出现多次如果不是因为时间序列展开而是因为数据拼接重复那这些重复样本会严重放大模型的训练权重。我在某次实操中发现同一个房源ID重复了4次而这条房源恰好是高价房源结果模型对高价房源的预测被这个重复样本带偏了。这种脏数据必须在数据探查阶段就揪出来。3. 特征工程决定baseline上限的隐藏战场如果你是竞赛老手可能知道特征工程才是baseline之间的分水岭。同样的LightGBM有些人打出来RMSE在75有些人直接在65以下——差距不来自模型而来自特征。价格预测任务的核心特征是“描述这个房源为什么值这个价”。3.1 文本特征的粗粒度处理不用上BERT也能涨点很多人在民宿价格预测里遇到文本字段就头大觉得必须上NLP模型。实际上基线阶段完全不用那么复杂。我对房源标题和描述的处理方式很简单标题文本统计长度和是否包含特定关键词。比如“海景”、“独栋”、“近地铁”、“带泳池”这些词对房价的影响是立竿见影的。我用一个30词左右的关键词词典对标题做布尔匹配直接生成30个0/1特征。这些特征的业务含义非常清晰LightGBM很容易学进去。描述文本统计长度、是否包含房东自我介绍、是否包含房屋守则。可能你会觉得这些和价格有什么关系实操中我发现描述长的房源通常房东更用心价格定位也更高这个弱特征聊胜于无。千万别在baseline阶段做太重的文本处理。我见过有人一上来就对几十万条描述跑BERT embedding结果训练时间翻了20倍RMSE掉了一丢丢还容易过拟合。文本的精细特征提取留给第二阶段优化就好。3.2 地理特征的“土办法”经纬度不只是两个数民宿价格地段决定一半。如果数据里有经纬度千万不要简单地把它们当成两个数值型特征丢进模型——模型学不出“海边比内陆贵”这种空间规律的。常见做法是聚类距离特征两件套。先对经纬度做KMeans聚类聚成100个簇左右每个簇作为一个类别特征簇编号。这一步能有效捕获城市内的微观区域差异比如“人民广场周围3公里”和“老城区”在房价上完全不同。KMeans聚类的类别数可以做一个简单的网格搜索不过100附近通常就够用了。然后计算每个房源到城市中心的距离、到最近的地铁站如果有POI数据的距离。这些距离特征不做归一化直接进树模型LightGBM这种基于分裂的模型对数值的量纲不敏感反而能自动找到合适的分裂点。如果数据里没给经纬度那就是另一个故事了。只能用“街区”或者“邮区”这样的字段做目标编码。目标编码的做法用该街区所有房源价格的均值或log价格均值作为特征值。注意一定要做交叉验证内的编码避免数据泄漏这个细节后面会详细说。3.3 时间特征的利用老房源就该比新房源便宜吗时间特征也是价格预测里的一个宝藏。民宿行业有个共识运营时间越长的房源如果积累了稳定的好口碑价格通常可以坚挺但设备老化、装修过时价格又会走弱。所以“上架天数”和“最近评论距今的天数”这两个特征能直接刻画房源的热度和维护状态。我的做法是days_since_listed从数据截止日期减去发布日期衡量房源“年龄”days_since_last_review从数据截止日期减去最近评论时间衡量房源的活跃度review_count_per_month评论总量除以运营月数评估房源的热度效率这些时间特征不需要做任何复杂变换直接进模型。实测下来days_since_last_review这个特征的特征重要性通常能排进前10——因为它同时承载了“房源是否还在运营”和“房源质量口碑”两层信息。3.4 避免数据泄漏目标编码的雷区特征工程里最常见的翻车事故就是目标编码时把测试集信息混进来。我在某次项目里用目标编码处理城市字段直接把全量数据里的价格均值算好再填回去结果线下验证RMSE漂亮得离谱但一上测试集就崩了。原因很清楚目标编码的均值是基于训练集和测试集的全部样本计算的这等于把测试集的答案偷偷喂给了模型。正确做法是在交叉验证的每一折里只用训练部分的均值去做编码。LightGBM的XGBRegressor或CatBoost里可以调target_encoding的原生参数但如果自己手动做我建议封装一个自定义的交叉验证编码器。实际操作里我一般这么写Flow对需要目标编码的每个类别字段在每一折CV内计算训练集的target均值用这个均值填充该折的训练集和验证集类别全量的测试集预测时用全部训练集的均值编码但这个均值只用不参与验证如果你懒直接在整个训练集上做目标编码再划分数据集你的loss曲线一定会给你一个“惊喜”。4. 建模与Baseline搭建LightGBM的常规但靠谱之路进入建模阶段我给baseline的选择是LightGBM没有之一。原因很简单树模型对数值和类别特征的混合处理非常友好训练速度快而且自带正则化不容易过拟合。神经网络在这个任务上不是不能打但baseline的定位决定了我们要用最短时间拿到一个稳定、可复现的分数。4.1 数据划分与交叉验证设计数据划分这里有个大学问。如果是纯随机赛题直接K折交叉验证没毛病。但民宿价格预测天然带时间属性测试集的房源大概率比训练集的房源“更晚发布”。直接随机划分会让模型在时间分布上吃亏。我的建议是先用时间排序再手动划分训练集和测试集。比如把最后10%-15%时间发布的房源作为验证集前面的做训练集。这样能模拟真实世界“用历史预测未来”的评估口径。如果赛题保证是随机打乱的那再切回普通KFold也来得及。我平时两种都会做先跑一个普通KFold快速看model稳定性再用时间划分做最终的模型评估。两边的RMSE如果差距很大说明模型在时间维度上泛化能力弱得回去补特征。4.2 模型参数选择的“常规操作”参考LightGBM的参数选择我分享一套我在类似任务上实测靠谱的初始配置import lightgbm as lgb params { objective: regression, metric: rmse, learning_rate: 0.05, max_depth: 6, num_leaves: 31, min_child_samples: 20, subsample: 0.8, colsample_bytree: 0.8, reg_alpha: 0.1, reg_lambda: 0.1, n_estimators: 2000, early_stopping_rounds: 100, random_state: 42, verbose: -1 }这套参数有几个细节值得展开讲讲num_leaves控制在31不是越大越好。叶子节点多了模型对噪声的拟合能力会上升但泛化能力会下降。价格预测这种噪声大的任务31已经够用了min_child_samples设20防止叶子节点里的样本太少模型学出极端价格colsample_bytree设0.8让每棵树只用80%的特征相当于自带特征随机化能有效抵抗特征间的多重共线性reg_alpha和reg_lambda是L1和L2正则化价格预测里的高维特征不少加了正则化能显著压过拟合实际训练时我一般先固定learning_rate0.05配合早停。早停轮数100的意思是连续100轮验证集RMSE没有改善就停止训练。这样做的好处是不用提前猜测n_estimators让模型自己决定训练多久。4.3 模型融合的入门操作为什么你的baseline不该只跑一个模型baseline做完了LightGBM能拿到不错的分数但你也许会好奇为什么有人能继续往下降2-3个点答案通常是模型融合。我的建议是在baseline阶段就把融合的基础打牢而不是等模型调优完毕再回头加。最简单的融合方法是加权平均。我实测下来LightGBM和XGBoost的融合效果不错CatBoost也可以参与进来。三个模型各自独立训练同样的特征拼起来但算法不完全相同最后预测结果线性加权。权重不是随便设的。我习惯用验证集上的RMSE来算假设LightGBM验证集RMSE70XGBoost73CatBoost75那么权重可以大致设为(1/70^2):(1/73^2):(1/75^2)归一化后作为最终加权系数。这个方法听起来很学术其实实操起来非常简单直接取1/rmse再归一化也行效果差不多。如果你觉得三个模型跑起来太耗时那就安心跑LightGBM单个模型。但如果你时间充裕融合是稳赚不亏的投资。baseline阶段就搭好融合的Pipeline后面调特征、调参数所有实验都跑在同一套融合体系中分数变化才有可比性。5. 结果评估与模型可解释性你的模型靠谱吗模型训练完不要急着提交或者收工。你需要回答三个问题模型预测的价格合理吗模型学到了什么规律哪些特征在驱动预测5.1 残差分析哪里预测偏了残差真实值 - 预测值的分布能告诉你模型在哪些地方系统性犯错。我习惯画一个残差直方图再按价格区间分桶看残差低价房源100-300元的残差通常是正偏预测偏高。模型倾向于把所有房源往中间价拉低价房源容易被高估高价房源1000元以上的残差通常是负偏预测偏低。因为高价房源样本本来就少模型学不到足够的极端模式如果发现这个模式说明特征工程没做足或者高价房源的特征没有有效建模。可以针对性补充“空间位置平台类型”的交互特征或者给高价房源的样本在损失函数里加权重。有人可能会问直接对高价房源样本加权不就行了理论上可以但会引入新的偏差实操起来不如加特征来得干净。我的经验是先试加特征实在不行再考虑调整采样。5.2 特征重要性读懂模型的黑盒LightGBM训练完成后直接输出feature_importances_能看到模型认为哪些特征最关键。我跑完这个baseline后特征重要性排在最前面的大概是目标编码后的街区占了大头经纬度聚类簇编号房屋类型房产容量最大住客数卧室数量运营时间相关特征描述文本长度这个排序本身就在告诉你这个模型在做定价时优先看的是“在哪儿”地理信息然后是“多大”物理空间最后才轮到“什么质量”文本、评分等。这个逻辑和民宿行业的常识是一致——地段决定基础价格硬件决定溢价空间服务决定口碑影响。如果特征重要性和你的业务直觉偏差很大比如描述文本的重要性排进前三那大概率是数据里有隐含的信号没被处理好。这时候我会回头检查是不是文本里某个关键词和价格高度相关比如“独栋别墅”这种本质上是物理空间特征的另一种表达方式。6. 常见问题与避坑实录那些让你白跑一晚上的坑做这类项目的过程中我踩过不少坑也帮朋友排查过不少问题。这里整理一份高频问题的速查表每一条都是我或者身边人真金白银试出来的。6.1 为什么我的RMSE一直降不下去这是被问得最多的问题。最常见的原因是没有做log变换直接对原始价格做回归。我反复提过价格分布是长尾的模型天然会被高频低价样本带跑。另一个原因是目标编码的泄漏导致线下分数虚高但线上不行。排查思路很简单先看训练集和验证集的RMSE差距。如果验证集略高一点点正常如果验证集比训练集高出一大截说明过拟合了需要加正则化参数、减特征如果验证集和训练集分数差距不大但绝对数值很高说明特征信息量不够需要回去挖特征。6.2 LightGBM训练“看起来”很快但真的收敛了吗LightGBM有verboseTrue的时候会打印每轮的训练日志但不建议完全依赖肉眼。更可靠的做法是画出训练过程的RMSE曲线确认验证集的曲线没有在某一轮之后开始上升。早停虽然能自动停下但画出来能让你直观判断模型是“欠拟合”还是“刚过拟合就停了”。我遇到过一种情况验证集RMSE在400轮时还在下降但500轮后开始反弹。如果你不看曲线直接拿了最后一个epoch的模型其实已经过拟合了。正确做法是记录早停时最优epoch的模型而不是最终模型。6.3 测试集和训练集的城市分布不一致怎么办这种情况在真实业务里太常见了——训练集里某个城市有5000套房源测试集里这个城市只有800套而另一个城市恰好反过来。树模型在这种情况下会表现得很差因为它在训练集里学到的城市样本分布和测试集完全对不上。我的应对办法训练时限制每棵树的复杂度同时把城市特征改成频次编码该城市在训练集里的房源数量加目标编码的组合。如果城市分布严重错位还可以考虑在训练时对这个城市的数据做降采样或者反过来让模型的注意力不集中在某一个城市上。这不是银弹但至少能保证不崩盘。6.4 测试集必须用同一套缺失值填充逻辑吗这是数据预处理里最容易被忽视的坑。你在训练集上用的缺失值填充值比如中位数在测试集上必须用同一个值。很多人在训练集上计算了median然后写代码时因为习惯原因在测试集上重新算了中位数两个中位数差一点点结果RMSE就莫名其妙高了一截。正确做法是在训练集上固定所有填充值、目标编码映射表、特征缩放参数然后打包成交叉验证Pipeline直接用transform统一应用到测试集。这也是为什么我强烈建议所有预处理都写成自定义Transformer类的函数而不是在Notebook里手动操作。6.5 丰富的特征一定好吗我删了反而涨分我在某个数据集上实验过加了30个文本关键词特征后发现验证集RMSE反而涨了1-2个点。查了特征重要性才发现这30个特征里有一半重要性极低纯粹是噪声。LightGBM虽然能自动忽略无关特征但在数据量不够大的情况下大量弱特征会干扰分裂点的选择。建议每加一批特征就做一轮验证记录特征数量和RMSE的变化。如果特征量大幅增加而RMSE没有显著下降果断回滚。价格预测的任务特征量控制在50-100个左右就足够撑起一个不错的baseline了没必要刻意堆到几百个。7. 写在最后的经验之谈以一个完整的baseline项目为例从拿到数据到把分数跑到能看我对时间分配的体会大概是30%花在数据探查和清洗上40%花在特征工程上20%花在模型调参和融合上最后10%用来做验证和复盘。很多新手把这个比例倒过来花80%时间反复调参效果却不尽如人意——本质是因为特征没到位模型再怎么调也学不到信号。如果你正准备上手民宿价格预测这类任务我这里有一个很具体的建议先交一个“粗糙但完整”的baseline再回头优化。不要试图第一步就做出完美特征组合而是先跑通一版记录下RMSE基准值再开始迭代。有了基准值你每做一件新事情都能明确看到“涨没涨分”这个反馈循环才是项目推进的核心动力。最后分享一个小技巧。每次实验我在代码里都会顺手保存三样东西特征列表、模型参数、验证集RMSE。三个月后回头看这个项目你就能完整还原每一步决定是怎么做的——这比任何花哨的复盘文章都管用。祝大家都能跑出一个让自己满意的baseline然后在这个基础上把分数一点点压下去那个过程才是做预测类项目最上瘾的地方。