贝壳2024秋招机器学习与数据挖掘笔试复盘:考点、思路与备战建议
这段时间正值秋招高峰刚参加完贝壳找房2024年秋招机器学习/数据挖掘工程师的第二批笔试。整体做下来和第一批考完的同学交流了一下发现题型框架基本一致但在部分细节上明显加深了。趁热打铁整理一份复盘笔记把题目还原、解题思路、踩坑点和后续准备建议都写清楚给后面批次的同学一个参考。1. 贝壳这批笔试的整体画像题量不大但处处是坑先说基本盘。整个笔试用时120分钟通过牛客网在线作答没有开摄像头。题型分为四块十几道单选题、四五道多选题、两道编程题、一道SQL分析题最后还有一道业务场景设计题。前两部分覆盖机器学习基础、概率统计、特征工程和少量深度学习常识。整体看下来贝壳的笔试题风格属于“基础题为主、业务结合度高、编程题不算难但需要仔细读题”的类型。印象最深的是客观题部分很多知识点不是直接问概念而是给你一个具体场景让你判断该用什么方法或哪个指标。比如有一道题问正负样本比例接近1:99时下面哪个评估指标最不适合用来衡量模型效果选项里有准确率、召回率、AUC、F1。这种题表面考指标实际考的是样本不均衡场景下各评估指标的适用边界。笔试时间分配我大致是客观题40分钟编程题35分钟SQL题20分钟业务题20分钟最后留5分钟检查。实际做下来时间刚好够没有特别紧张但多选题比较磨人因为选错、少选都不得分宁可少选也要保正确率。从内容占比来看机器学习基础与模型评估大约占了65%特征工程和数据预处理占了15%概率统计占了10%深度学习和工程向的内容占了10%左右。这个分布也对应了贝壳业务对候选人的期待既要懂算法原理也要有数据敏感度和业务视角。2. 客观题考点那些藏在基础概念中的得分点与失分点2.1 模型评估与样本不均衡场景化提问防不胜防贝壳的客观题很喜欢把模型评估指标放在业务场景里考。除了上面提到的正负样本比例1:99那个题还有一道多选是关于AUC和PR曲线的。题目大概是在一个用户转化率预测任务中正样本占比非常低以下描述正确的是。这种题如果对PR曲线和ROC曲线的差异理解不透彻很容易选错。关键知识点在于ROC曲线对样本类别分布不敏感适合评估整体排序能力但在正样本极度稀疏的情况下PR曲线能更直观反映模型对正类的识别效果因为PR曲线的精确率和召回率都直接聚焦于正类。业务上如果更关注少数类比如转化用户、虚假房源PR曲线往往比ROC曲线更有参考价值。还有一个印象深刻的题是问在A/B测试中某策略组的转化率比对照组高20%但p值为0.07以下哪个判断是正确的这个题考的是假设检验和p值理解的边界。很多人会直接选“该策略显著优于对照组”但实际上p值大于0.05意味着在95%置信水平下不能拒绝原假设结果并不显著。选项里还有一个干扰项是“p值表示策略无效的概率”这个也是典型误区p值不是H0为真的概率。2.2 特征工程连续特征离散化和缺失值处理的细节有一道题特别有意思问的是对连续特征做离散化后模型训练效果提升最可能的原因是。备选答案包含了降低异常值干扰、增强特征鲁棒性、引入非线性、减少过拟合等。做过实际项目的人都知道连续特征离散化本质上是给模型引入了非线性映射能力同时对异常值不敏感。比如年龄特征直接输入线性模型年龄和收入可能不是线性关系但分箱之后每个箱体对应一个独立权重就能很好地捕捉这种非线性。这类题如果只背概念不做项目很容易漏选“引入非线性”这个关键点。缺失值处理的题也考了给出四个场景让选合适的处理方式某特征缺失率达到60%但可能与目标强相关、某特征缺失完全随机、某特征缺失与目标标签相关等。核心逻辑是区分完全随机缺失、随机缺失和非随机缺失。与标签相关的缺失本身可能携带信息需要单独编码而不是简单填充。2.3 GBDT与XGBoost的区别从原理层面理解而不是背结论多选题里有一道关于XGBoost相比GBDT的改进选项包括了支持二阶泰勒展开、加入正则化项控制模型复杂度、支持列采样、内置缺失值处理。这题基本属于送分题但容易漏选的是“支持列采样”因为很多人只记住了XGBoost加了正则和二阶导忽略了它对随机森林列采样的借鉴。我在复习时自己整理过一个对比表把GBDT、XGBoost、LightGBM的核心差异列出来考试时遇到这类题就很快维度GBDTXGBoostLightGBM目标函数一阶导数二阶泰勒展开二阶泰勒展开正则化无显式正则叶子节点数L2叶子节点数L2特征分裂预排序预排序近似直方图基于梯度的单边采样互斥特征绑定缺失值处理无自动学习缺省方向自动学习缺省方向训练速度较慢较慢快还有一道SVM相关的题给了一个非线性可分数据集的场景问选择哪个核函数最合适。这个按经验选RBF核就行但要理解RBF核本质上是在做特征映射到无穷维对非线性边界拟合能力强不过对参数敏感核函数系数过大会过拟合。2.4 概率统计贝叶斯公式是每年必考的钉子户贝叶斯公式几乎是所有机器学习笔试必考的知识点。贝壳这批考了一道这样的题某房源被用户收藏的概率是8%其中真实优质房源占60%被收藏普通房源只有5%被收藏。若一个房源被收藏了问它是优质房源的概率是多少。这题需要设优质房源的先验比例并套用贝叶斯公式计算后验概率。我当时算的思路是设总体房源中优质房源比例为p则被收藏且为优质的概率是0.6p被收藏且为普通的概率是0.05(1-p)。根据全概率公式被收藏的总概率是0.6p 0.05(1-p)题目给出这个值等于8%先解出p再计算条件概率。这类题只要把公式记牢把变量的含义捋清楚一般不会出错。2.5 正则化与过拟合L1和L2的区别要能从几何角度解释有一道多选题让选出关于L1和L2正则化的正确说法。选项中涉及L1正则化更容易产生稀疏权重、L2正则化会让权重更平滑、L1等价于拉普拉斯先验、L2等价于高斯先验。前三个都容易判断第四个需要知道贝叶斯视角下正则化项对应参数先验分布L1对应拉普拉斯先验、L2对应高斯先验。复习时还常被问到在高维稀疏特征场景下为什么L1比L2更常用。因为L1正则化会把不重要的特征权重压缩到0天然做了特征选择在高维场景下能显著降低存储和计算开销。L2只是让权重趋向于0但不会等于0稀疏性不够。3. 编程题一个考特征工程直觉一个考排序评估逻辑3.1 第一题基于用户行为日志构建活跃度特征题目大概意思是给定一个用户行为日志表每一行是用户对房源的一次行为行为类型包括浏览、收藏、约看、成交。权重分别是1、2、3、5。现在需要实现一个函数输入这个列表输出每个用户活跃度最高的Top10房源按照用户维度分组并返回用户ID、房源ID和累计活跃度得分。这道题本质上考察的是分组聚合TopN排序。用Python写的话最自然的做法是用defaultdict嵌套字典遍历日志累加得分最后对每个用户的房源字典按分数排序取前10。需要注意几个细节同一用户对同一房源可能有多条行为记录需要累加如果不同房源得分相同按房源ID升序输出避免结果顺序不稳定。我当时的参考实现思路如下from collections import defaultdict def top_houses(logs): user_house_score defaultdict(lambda: defaultdict(int)) weight_map {view: 1, favorite: 2, appoint: 3, deal: 5} for user_id, house_id, action in logs: user_house_score[user_id][house_id] weight_map[action] result [] for user_id, house_scores in user_house_score.items(): sorted_houses sorted(house_scores.items(), keylambda x: (-x[1], x[0])) for house_id, score in sorted_houses[:10]: result.append((user_id, house_id, score)) return result这道题不难但要注意时间复杂度和空间复杂度。如果日志量很大每个用户的房源数量也很大全量排序后取Top10是可以接受的。但如果要在生产环境做更优的做法是用heapq维护每个用户一个大小为10的最小堆把排序复杂度从O(nlogn)降到O(nlog10)。笔试时间有限我直接用了排序方案思路清晰代码量也少。对于类似场景如果是几十万用户、几千万行为日志的规模用堆方案会稳妥很多因为全量排序在内存和时间上都会吃紧。3.2 第二题实现NDCGK评估指标第二题明显比第一题更有区分度。题目设定是在搜索结果中真实相关程度用0到3分表示3分最相关。给定一个模型的排序结果房源ID列表和对应的真实相关分列表要求实现一个函数计算NDCGK。这题考察的是推荐/搜索排序场景下的标准评估指标实现非常贴近贝壳搜索排序的业务。如果之前没有接触过NDCG可能连公式都记不住。NDCG的全称是Normalized Discounted Cumulative Gain核心思想是排序越靠前的相关文档增益应该越大同时要做归一化消除不同查询下相关文档总数不同的影响。计算流程分三步第一步计算DCGK。对每个位置i从1开始贡献值是(2^reli - 1) / log2(i 1)把所有位置的贡献值累加。第二步计算IDCGK。把真实相关分从大到小排序取前K个按照同样的公式计算理想情况下的DCG。第三步NDCGK等于DCGK除以IDCGK。参考实现import math def ndcg_at_k(predicted_houses, relevance_scores, k): # 取前k个 pred_k predicted_houses[:k] rel_k relevance_scores[:k] dcg 0.0 for i, rel in enumerate(rel_k): dcg (2 ** rel - 1) / math.log2(i 2) # 理想排序按真实相关分降序 ideal_rel sorted(relevance_scores, reverseTrue)[:k] idcg 0.0 for i, rel in enumerate(ideal_rel): idcg (2 ** rel - 1) / math.log2(i 2) return dcg / idcg if idcg 0 else 0.0这道题有几个容易出错的地方。第一个是分母的位置偏移log2(i1)在Python代码里要写成math.log2(i2)因为索引从0开始。第二个是当IDCG为0时即前K个真实相关分全为0需要做保护直接返回0否则会出现除零异常。第三个是K大于列表长度时的边界处理要明确是取全部还是补0题目里会说明如果没说通常按取全部处理。还有一道小题回顾起来也值得说——要求写一个函数对价格特征做z-score标准化。这个更简单但需要注意标准差计算时要用总体标准差除以n而不是样本标准差除以n-1因为在实际特征工程中对训练集做标准化时用的是总体统计量。4. SQL分析题别急着写代码先把统计口径定明白SQL题给的是一个简化版的用户行为数据表包含三张表用户表、房源表、用户行为日志表。题目要求统计2024年1月1日活跃用户在1月8日7天后的留存情况并按城市维度输出留存率。这类题在贝壳的业务场景里非常常见数据分析团队经常要统计各类留存指标。但笔试题的坑不在SQL语法本身而在于统计口径。第一个口径问题什么叫“1月1日活跃用户”一般指当天有任意行为记录的用户需要去重同一用户当天有多条行为只能算一次。第二个口径问题“1月8日留存”如何判定通常是指这波用户在1月8日当天仍有行为记录。但这里有一个隐含分支——统计7日留存时到底是算第7天当天留存还是算1月2日到1月8日之间的活跃留存。不同业务定义不同。我在笔试时选择了“第7天当天有活跃行为”这个口径因为题目表述写的是“1月8日7天后的留存情况”。第三个问题城市维度怎么关联。行为日志表中没有城市字段需要先关联房源表拿到城市ID再关联城市表拿到城市名称。如果城市表里部分城市在1月1日没有活跃用户按内连接还是左连接处理题目要求是“输出所有城市的留存率”这里需要用左连接保留那些可能没有活跃用户的城市但要注意分母为0的情况。参考SQL大致长这样with active_users as ( select distinct user_id from action_log where action_date 2024-01-01 ), retained_users as ( select distinct a.user_id from active_users a join action_log l on a.user_id l.user_id and l.action_date 2024-01-08 ), user_city as ( select l.user_id, h.city_id from ( select user_id, house_id, max(action_date) as last_action_date from action_log where action_date in (2024-01-01, 2024-01-08) group by user_id, house_id ) l join house h on l.house_id h.house_id ) select c.city_name, count(distinct uc.user_id) as active_user_cnt, count(distinct ru.user_id) as retained_user_cnt, count(distinct ru.user_id) / count(distinct uc.user_id) as retention_rate from user_city uc left join city c on uc.city_id c.city_id left join retained_users ru on uc.user_id ru.user_id group by c.city_name, c.city_id order by retention_rate desc;这道题我踩了一个小坑一开始直接用action_log和house表做join导致同一个用户如果当天活跃多个房源会产生多条记录后面用count(distinct)才兜住。如果当时直接count(1)计算用户数结果会虚高不少。这种问题在真实业务中也经常出现——先明确粒度再决定用什么聚合方式。考完之后复盘其实还有更简洁的写法。用窗口函数先给每个用户标出1月1日是否活跃、1月8日是否活跃然后一次性聚合select c.city_name, count(distinct if(active_flag 1, u.user_id, null)) as active_users, count(distinct if(active_flag 1 and retain_flag 1, u.user_id, null)) as retained_users, count(distinct if(active_flag 1 and retain_flag 1, u.user_id, null)) / count(distinct if(active_flag 1, u.user_id, null)) as retention_rate from ( select user_id, max(if(action_date 2024-01-01, 1, 0)) as active_flag, max(if(action_date 2024-01-08, 1, 0)) as retain_flag from action_log where action_date in (2024-01-01, 2024-01-08) group by user_id ) u left join ... -- 关联城市 group by c.city_name;这种方式在数据量大时性能更好逻辑也更集中。笔试时如果能写出这种版本面试官观感会更好。5. 业务设计题贝壳真正想考察的是落地思维业务题有两道要求任选一道作答。我选的是“设计一个二手房估价模型并说明特征、模型选择、评估指标和上线方案”。这道题相当贴近贝壳的主营业务也比较能体现一个算法工程师的综合能力。回答这类题目我的框架是先定义目标和边界再拆解数据与特征然后对比模型方案并给出评估方式最后说清楚落地和迭代。问题定义方面估价对象是挂牌二手房目标是预测房源的市场参考价而不是成交价。因为成交价受议价空间、买卖双方心理因素影响比较大噪声高。预测的粒度可以是房源级别也可以先预测小区均价再结合房源属性做修正。实际业务中通常是两者结合。特征工程部分我分了四层来回答第一层是房源静态特征比如建筑面积、户型、楼层、朝向、楼龄、装修程度、是否有电梯。这些特征基本决定了房源的物理价值。第二层是小区特征包括小区均价、绿化率、容积率、物业费、建造年代。贝壳在这方面有天然的数据优势小区是核心组织单元。第三层是区位特征包括距离最近地铁站的距离、周边学校等级、商圈成熟度、医院配套。这类特征对房价影响非常大但需要用POI数据和地图数据加工。第四层是市场特征比如该小区近30天成交均价、挂牌量、去化周期。市场供需会直接影响价格预期。模型选择方面表格类数据优先考虑LightGBM或XGBoost。原因有几个特征中既有数值型也有类别型树模型对特征尺度不敏感可以自动处理缺失值和异常值训练效率高迭代成本低可解释性总体优于深度模型。深度学习在这个场景并不是最优选择除非数据量特别大、特征之间关系非常复杂。评估指标方面不能只看MAE。我建议关注三个维度MAE衡量平均绝对误差MAPE衡量相对误差还要看定价误差超过5%的样本占比因为业务上如果偏差过大会影响用户体验。另外要按城市、小区、户型维度做分层评估防止某些细分群体被系统性低估或高估。上线方案方面需要考虑模型更新的频率。房价有明显的时效性模型不能训练一次用一年。比较稳妥的方式是每周用最新成交数据做增量训练每天用当天新增的挂牌和成交数据做特征更新。监控方面要关注线上预测价格分布和实际成交价格分布的偏移比如连续一周预测价偏高就要进入排查流程。第二道业务题的选项是“设计一个小区的房源排序模型”。思路类似但重点换成了用户行为特征用户在搜索列表页的点击、详情页停留时长、收藏、约看、经纪人咨询等行为结合房源属性和用户画像做个性化排序。评估指标自然就是均值NDCG或者TopK命中率。这类业务题没有标准答案核心是看候选人能不能把问题拆解清楚有没有真实项目经验。千万不能只堆模型名词比如一会儿用FM一会儿用DeepFM但说不清为什么选它。贝壳这类业务导向的公司更看重的是你对数据、业务、模型的综合理解。6. 复盘思考哪些准备真正派上了用场6.1 机器学习基础的系统复习是最划算的投入这次笔试给我最大的感受是贝壳不管业务怎么复杂笔试阶段还是老老实实考基础。决策树和信息增益的计算、SVM核函数的选择、朴素贝叶斯的条件独立性假设、集成学习的方差偏差分析这些经典考点一个都没少。如果你现在是在准备秋招的早期阶段我的建议是不要急着刷一堆冷门论文或高阶模型先把西瓜书和统计学习方法里的核心概念吃透做到能用自己的话解释、能推导简单公式、能结合场景判断题目的对错。基础题在笔试里占比高、得分确定性也高是性价比最高的复习项。我在考前两周做了这样一件事把机器学习的核心知识点按考点清单整理出来每个考点附上一道练习题每次复习时先合上笔记回忆一遍再对照检查。这样到考试时很多题目其实已经见过了。6.2 编程题别只刷LeetCode业务场景题更重要LeetCode上的经典算法题在笔试中不是重点。贝壳编程题的难度不高但背景都是业务相关的——行为日志处理、特征构建、排序评估。这种题目更考验的是“用代码解决实际数据问题”的能力。我当时复习方向就放在用Python熟练处理字典、集合、列表的嵌套操作熟悉排序函数的key参数用法掌握collections模块里的defaultdict和Counter会用heapq做TopN。这些都是处理数据类编程题的基础工具。如果能把pandas的基本操作也掌握写这类代码会更快但笔试环境不一定提供pandas所以原生Python的写法必须要熟练。6.3 SQL的窗口函数和统计口径是性价比很高的方向SQL这道题如果之前没写过窗口函数可能也有办法通过子查询和group by解决但会麻烦很多。建议集中练习一下row_number、rank、sum over、if结合聚合这类常见写法。还有就是要养成先确认统计口径再动笔的习惯——时间范围怎么定、是否去重、连接方式选哪种、分母为0怎么处理这些想清楚再写SQL往往能一步写对。6.4 业务题提前了解公司业务能显著提升回答质量贝壳的业务核心是居住服务围绕房产交易、租赁、家装等场景。笔试前可以花一个小时看看贝壳App里有哪些核心功能思考每个功能背后需要什么样的算法支持。比如搜索排序、房源估价、智能推荐、虚假房源识别这些都是很典型的算法落地场景。提前在心里过一遍流程现场遇到业务题时就不会慌。最后再说一个细节笔试时我习惯先把所有题目快速浏览一遍对每道题的难度和时间做个大致估算。比如这道SQL题比较费时间就标记一下先做后面的业务题最后再回头处理。这种时间管理策略在这次笔试里帮了我不少因为客观题里的多选题确实比预想中消耗时间。整体来看贝壳这批笔试的风格务实、贴近业务、不炫技认真准备过机器学习基础的人会比较占优势。如果你的方向也是算法岗希望这份复盘能帮你少走一些弯路。