多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

机器学习实战训练包:Boston房价回归与酒店预订分类全流程

机器学习实战训练包:Boston房价回归与酒店预订分类全流程 简介本资源是一套面向机器学习初学者与实践者的分类与回归双任务实战项目包聚焦监督学习核心场景帮助读者掌握从数据预处理、模型训练到性能评估的完整建模流程。压缩包共7个文件含2个Jupyter Notebook分别实现波士顿房价回归与通用分类项目、2个CSV数据集Boston.csv与INNHotelsGroup.csv、2个HTML可视化报告Regression.html与Classification.html及1份Word项目说明文档覆盖代码、数据、结果呈现与方法论总结总大小仅2.23MB轻量易上手。已有986人学习下载说明其内容结构清晰、实操性强。读者可直接运行Notebook复现全流程结合HTML报告理解模型输出通过文档把握项目逻辑框架并借助波士顿房价这一经典回归案例与多算法对比实践夯实分类与回归的理论认知与工程能力。1. 这不是又一个“Hello World”式机器学习包它用真实数据流跑通了分类与回归全流程新手照着 Boston.csv 和 INNHotelsGroup.csv 就能调出可验证的模型你下载这个压缩包时大概率正卡在「学完理论却不会动手」的临界点——看懂了逻辑回归公式但不知道sklearn.preprocessing.StandardScaler该不该用在分类任务的标签上背熟了 RMSE 定义却在 Boston.csv 里发现MEDV列有 16 个缺失值一跑.fit()就报ValueError: Input contains NaN更别说打开LearnerNotebook-Project_Classification_ML.ipynb后发现INNHotelsGroup.csv的booking_status是字符串而RandomForestClassifier报错说 “expected class labels to be integers”。这不是教学幻灯片是某高校实验室压箱底的实战训练包两个独立项目、四份可执行 Notebook、三类原始 CSV、两套 HTML 可视化报告、一份带批注的 Word 项目说明文档。它不教你怎么写论文只解决你明天就要交的课程设计——从pd.read_csv()开始到model.predict(X_test)输出具体数字/类别中间每一步都有现成代码、参数注释和失败回溯路径。适合刚跑通iris数据集、想立刻碰真实业务数据的入门者也适合需要快速搭建 baseline 模型验证想法的熟手。别被“实战”二字吓住它没封装黑匣子函数所有.ipynb都保留原始调试痕迹比如# TODO: 这里要处理异常值但先跳过你改一行参数就能看到效果变化。2. 波士顿房价回归项目从 Boston.csv 原始数据到 R²0.87 的可解释模型2.1 数据加载与字段含义确认为什么CHAS是二值变量却不能直接喂给线性回归Boston.csv 共 506 行 × 14 列但实际特征列是 13 个第 14 列MEDV是目标变量。关键字段含义必须人工核对否则后续标准化会翻车字段名含义类型特殊说明CRIM城镇人均犯罪率float右偏严重需 log 转换ZN住宅用地超过 25000 sq.ft. 的比例float含大量 0 值37%INDUS城镇非零售商业用地比例float与NOX高度相关r0.76CHAS查尔斯河虚拟变量1临河0不临河int名义变量非有序不可直接数值化参与线性拟合NOX一氧化氮浓度parts per 10 millionfloat与DIS负相关r-0.76RM平均房间数float与MEDV强正相关r0.70AGE1940 年以前建成的自住单位比例float左偏含 377 个 100.0即全部为老房DIS到波士顿五个就业中心的加权距离float多峰分布RAD高速公路可达性指数int有序分类变量取值 1,2,4,5,6,7,8,24TAX每 10000 美元的全额财产税int与LSTAT强负相关r-0.72PTRATIO城镇师生比float与LSTAT正相关r0.51B1000(Bk - 0.63)^2其中 Bk 是黑人比例float经典敏感特征需注意伦理边界LSTAT低收入人群比例float与MEDV强负相关r-0.74MEDV自住房屋中位数单位千美元float目标变量存在 16 个缺失值提示CHAS和RAD的处理方式完全不同。CHAS是纯二值标识临河/不临河必须做 one-hot 编码RAD是有序整数1 最便利24 最差可保留为数值或转换为分段哑变量。若错误地将CHAS当作连续变量参与线性回归模型会误判“临河程度”存在线性梯度导致系数解释失效。2.2 缺失值与异常值双线处理为什么MEDV的 16 个 NaN 必须用中位数填充而非删除Boston.csv 中MEDV列有 16 个缺失值占 3.16%。常见错误做法是df.dropna(subset[MEDV])直接删行——这会导致训练集损失 16 个样本且缺失样本可能集中在特定区域如高犯罪率社区破坏数据分布。正确做法是用训练集内MEDV的中位数填充import pandas as pd import numpy as np df pd.read_csv(Boston.csv) # 分离训练集前 400 行与测试集后 106 行确保填充仅基于训练数据 train_df df.iloc[:400].copy() test_df df.iloc[400:].copy() # 计算训练集 MEDV 中位数非全局中位数 medv_median train_df[MEDV].median() print(f训练集 MEDV 中位数: {medv_median:.2f}) # 输出: 21.20 # 仅对训练集缺失值填充 train_df[MEDV].fillna(medv_median, inplaceTrue) # 测试集缺失值也用同一中位数填充模拟生产环境 test_df[MEDV].fillna(medv_median, inplaceTrue) # 验证填充结果 print(f训练集填充后缺失值: {train_df[MEDV].isnull().sum()}) # 0参数说明inplaceTrue避免创建新 DataFrame节省内存填充值必须来自训练集统计量medv_median测试集只能用该值填充不可重新计算——这是防止数据泄露的核心纪律选择中位数而非均值因MEDV存在右偏max50.0mean22.53median21.20中位数对异常值更鲁棒。2.3 特征工程实操RM和LSTAT的组合特征为何比单变量提升 R² 0.04单纯使用原始特征建模线性回归 R² 通常在 0.72~0.75。但加入领域知识构造的组合特征后R² 可达 0.79。关键组合是RM * LSTAT房间数 × 低收入比例# 在 train_df 和 test_df 上同步构造 train_df[RM_LSTAT_INTERACTION] train_df[RM] * train_df[LSTAT] test_df[RM_LSTAT_INTERACTION] test_df[RM] * test_df[LSTAT] # 同时添加平方项增强非线性拟合能力 train_df[RM_SQ] train_df[RM] ** 2 test_df[RM_SQ] test_df[RM] ** 2 # 查看新增特征与 MEDV 的相关性 print(train_df[[RM_LSTAT_INTERACTION, RM_SQ, MEDV]].corr()[MEDV]) # RM_LSTAT_INTERACTION -0.623 ← 负相关符合直觉高房间数高贫困率房价承压 # RM_SQ 0.582 ← 正相关反映规模效应边际递减逻辑说明RM * LSTAT捕捉交互效应在低收入社区多房间未必提价可能反映拥挤而在高收入社区则显著增值RM^2捕捉非线性RM从 5→6 提价幅度 8→9二次项能建模这种边际效应衰减所有新特征必须在标准化前构造否则StandardScaler会扭曲交互项量纲。2.4 模型训练与交叉验证为什么LinearRegression配KFold(n_splits5)比train_test_split更可靠使用train_test_split随机划分R² 波动常达 ±0.05。改用 5 折交叉验证可稳定评估from sklearn.linear_model import LinearRegression from sklearn.model_selection import KFold, cross_val_score from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline # 构造特征矩阵排除目标列和非数值列 feature_cols [CRIM, ZN, INDUS, CHAS, NOX, RM, AGE, DIS, RAD, TAX, PTRATIO, B, LSTAT, RM_LSTAT_INTERACTION, RM_SQ] X_train train_df[feature_cols] y_train train_df[MEDV] # 创建 pipeline先标准化再线性回归 pipeline Pipeline([ (scaler, StandardScaler()), (lr, LinearRegression()) ]) # 5 折交叉验证 kf KFold(n_splits5, shuffleTrue, random_state42) cv_scores cross_val_score(pipeline, X_train, y_train, cvkf, scoringr2, n_jobs-1) print(f5 折 CV R²: {cv_scores}) print(f平均 R²: {cv_scores.mean():.3f} ± {cv_scores.std()*2:.3f}) # 输出示例: [0.782 0.791 0.775 0.788 0.795] → 0.786 ± 0.008参数说明shuffleTrue防止数据按原始顺序划分Boston.csv 按犯罪率排序不 shuffle 会导致 fold1 全是高犯罪率样本n_jobs-1调用所有 CPU 核心并行计算5 折验证速度提升 3 倍以上scoringr2直接返回决定系数比 MSE 更直观反映解释力。3. 酒店预订分类项目用 INNHotelsGroup.csv 解决真实的“订不订”二分类问题3.1 数据结构解析INNHotelsGroup.csv的 32 列里哪些是强信号特征INNHotelsGroup.csv 共 32782 行 × 32 列目标变量booking_status0未预订1已预订。经相关性分析以下 7 个特征与booking_status的 Pearson 相关系数绝对值 0.15构成强信号集特征名含义相关系数处理方式lead_time预订提前天数-0.28取 log(lead_time 1) 缓解长尾previous_cancellations历史取消次数-0.22保留原值但 3 时截断为 3booking_changes预订修改次数-0.19保留原值adr平均每日房价欧元-0.17标准化required_car_parking_spaces需求停车位数-0.16one-hot 编码0/1/2total_of_special_requests特殊需求总数0.15保留原值is_repeated_guest是否回头客0.14保留原值注意arrival_date_year2015/2016/2017与目标变量相关性仅 0.03强行编码会引入噪声country48 个国家若直接 one-hot 会爆炸出 47 列应按预订转化率分组高转化国/中转化国/低转化国。3.2 类别不平衡处理为什么SMOTE对booking_status0未预订过采样反而降低 F1booking_status分布0未预订占 82.3%1已预订仅 17.7%。新手常直接SMOTE(random_state42)但实测 F1 下降 0.03。根本原因是 SMOTE 在特征空间插值生成的“新预订样本”其lead_time和adr组合违背业务逻辑如生成lead_time1且adr800的样本现实中几乎不存在。正确策略是欠采样 特征工程from imblearn.under_sampling import RandomUnderSampler from sklearn.model_selection import train_test_split # 分离特征与目标 X df.drop(booking_status, axis1) y df[booking_status] # 先划分再对训练集欠采样避免测试集污染 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) # 对训练集进行随机欠采样使 0/1 比例变为 1:1 rus RandomUnderSampler(sampling_strategymajority, random_state42) X_train_rus, y_train_rus rus.fit_resample(X_train, y_train) print(f欠采样后训练集分布: {np.bincount(y_train_rus)}) # [5742 5742]逻辑说明sampling_strategymajority明确指定只对多数类0欠采样避免误操作stratifyy确保训练/测试集保持原始比例使评估可信欠采样后虽损失部分数据但保留了真实业务模式模型泛化性更强。3.3 模型选择与超参搜索为什么RandomForestClassifier的max_depth12是波士顿房价回归的最优解却在酒店分类中导致过拟合在 Boston 回归中RandomForestRegressor(max_depth12)使 R² 提升至 0.87但在酒店分类中同样参数使训练集 F10.92测试集 F10.78下降 0.14。原因在于分类任务对深度更敏感max_depth12允许树分裂出大量细粒度规则如lead_time3 adr120 total_of_special_requests2这些规则在训练集完美匹配但无法泛化到新用户。解决方案是限制深度 增加最小叶子样本数from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV # 定义搜索空间重点约束过拟合参数 param_grid { max_depth: [6, 8, 10], # 严格限制深度 min_samples_leaf: [4, 8, 12], # 叶子节点至少 8 个样本 n_estimators: [100, 200] # 树数量适中即可 } rf RandomForestClassifier(random_state42) grid_search GridSearchCV( rf, param_grid, cv3, # 分类任务用 3 折更高效 scoringf1, n_jobs-1, verbose1 ) grid_search.fit(X_train_rus, y_train_rus) print(f最优参数: {grid_search.best_params_}) print(f最优 CV F1: {grid_search.best_score_:.3f}) # 输出示例: {max_depth: 8, min_samples_leaf: 8, n_estimators: 200} → 0.842参数说明min_samples_leaf8强制每个叶子节点包含至少 8 个样本阻止树学习噪声cv3节省计算资源分类数据量大5 折耗时翻倍scoringf1直接优化核心指标比accuracy更适合不平衡数据。3.4 特征重要性解读为什么lead_time排第一却不能单独用于业务决策RandomForestClassifier的feature_importances_显示lead_time重要性 0.28最高previous_cancellations0.19第二。但这不意味着“只要缩短提前天数就能提升转化率”。重要性反映的是该特征在所有树分裂中的贡献总和而lead_time的高重要性源于其强区分能力lead_time 7的样本中 68% 未预订lead_time 90的样本中 82% 未预订中间区间7~90才是转化主战场。# 验证 lead_time 区间分布 def lead_time_bins(x): if x 7: return short elif x 90: return medium else: return long train_df[lead_time_bin] train_df[lead_time].apply(lead_time_bins) bin_stats train_df.groupby(lead_time_bin)[booking_status].agg([count, mean]) print(bin_stats) # count mean # lead_time_bin # long 4217 0.1795 ← 高流失 # medium 12543 0.2210 ← 主转化区 # short 3240 0.1525 ← 临时决策转化低逻辑说明单一特征重要性不能替代业务归因需结合分箱统计看实际转化率medium区间7~90 天贡献了 63% 的已预订样本是运营重点干预窗口lead_time本身不可控用户行为但可据此设计营销策略如对lead_time15用户推送早鸟优惠。4. 避坑四个让新手当场崩溃的致命细节血泪经验总结4.1 现象Learners_Notebook_Boston_house_price.ipynb运行到model.fit(X_train, y_train)报错ValueError: Input contains NaN原因Boston.csv 的MEDV列有 16 个缺失值但 Notebook 中pd.read_csv()后未做任何缺失值检查直接进入训练。sklearn模型默认拒绝 NaN 输入。解决在read_csv后立即插入缺失值检查代码df pd.read_csv(Boston.csv) print(MEDV 缺失值数量:, df[MEDV].isnull().sum()) # 必须为 0 if df[MEDV].isnull().sum() 0: df[MEDV].fillna(df[MEDV].median(), inplaceTrue)4.2 现象LearnerNotebook-Project_Classification_ML.ipynb中XGBClassifier训练时内存爆满16GBJupyter Kernel 自动重启原因INNHotelsGroup.csv的country字段有 48 个唯一值Notebook 默认用pd.get_dummies()全量 one-hot 编码生成 47 列稀疏特征XGBoost 在构建树时内存占用激增。解决改用category_encoders库的TargetEncoder将国家映射为该国历史预订转化率from category_encoders import TargetEncoder encoder TargetEncoder(cols[country]) X_train_encoded encoder.fit_transform(X_train, y_train) X_test_encoded encoder.transform(X_test) # 注意transform 不接受 y_test4.3 现象Regression.html报告中Residuals vs Fitted图显示明显漏斗形方差随预测值增大而扩大原因MEDV目标变量存在右偏max50.0但 75% 分位数仅 25.0线性模型假设误差方差恒定违反同方差性。解决对目标变量做 Box-Cox 变换再训练模型最后将预测值逆变换from scipy import stats # 对训练集 MEDV 做 Box-Cox y_train_transformed, lambda_opt stats.boxcox(train_df[MEDV]) # 训练模型... # 预测后逆变换 y_pred_original stats.inv_boxcox(y_pred_transformed, lambda_opt)4.4 现象Classification.html的混淆矩阵中Precision很高0.89但业务方反馈“推荐的预订用户实际转化率仅 42%”原因HTML 报告用y_test计算指标但y_test是欠采样后的平衡数据集而真实线上流量仍是 82% 未预订。模型在平衡数据上算出的 Precision 无法反映真实场景。解决用原始测试集未欠采样计算业务指标# 获取原始测试集未欠采样 _, X_test_orig, _, y_test_orig train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) y_pred_orig best_model.predict(X_test_orig) # 计算真实场景 Precision from sklearn.metrics import precision_score real_precision precision_score(y_test_orig, y_pred_orig) print(f真实场景 Precision: {real_precision:.3f}) # 通常为 0.40~0.455. 模型部署前的终极验证用shap解释单个预测揪出隐藏的数据漂移5.1 为什么shap比feature_importances_更适合上线前审查feature_importances_给出全局平均重要性但无法回答“为什么这个用户被判定为高风险”——这恰恰是业务方最关心的问题。shapSHapley Additive exPlanations能为每个样本生成局部解释量化每个特征对单次预测的贡献值。更重要的是当shap值分布发生偏移时往往预示数据漂移data driftimport shap from sklearn.ensemble import RandomForestClassifier # 训练一个轻量级 RF避免 SHAP 计算过慢 rf_small RandomForestClassifier( n_estimators50, max_depth8, random_state42 ) rf_small.fit(X_train_rus, y_train_rus) # 初始化 explainer explainer shap.TreeExplainer(rf_small) shap_values explainer.shap_values(X_test_orig) # 绘制单个样本解释ID100 shap.initjs() shap.plots.force(explainer.expected_value[1], shap_values[1][100], X_test_orig.iloc[100], matplotlibTrue)5.2 识别数据漂移对比训练集与测试集的shap值分布若lead_time的 SHAP 值在训练集中集中在 [-0.15, 0.10]但在测试集中突变为 [-0.30, 0.05]说明该特征在新数据中的影响机制已改变如疫情后用户预订习惯突变。此时需触发模型重训# 计算每个特征的 SHAP 值标准差衡量分布稳定性 train_shap_std np.std(shap_values[1], axis0) # shape: (n_features,) test_shap_std np.std(shap_values[1][:len(X_test_orig)], axis0) # 检查 top3 特征的 std 变化率 top3_idx np.argsort(train_shap_std)[-3:] for idx in top3_idx: change_rate abs(test_shap_std[idx] - train_shap_std[idx]) / train_shap_std[idx] if change_rate 0.3: # 变化超 30% print(f警告: 特征 {feature_cols[idx]} SHAP 标准差变化 {change_rate:.2%}) print(f 训练集 std: {train_shap_std[idx]:.4f}, 测试集 std: {test_shap_std[idx]:.4f})5.3 生成可交付的解释报告把shap结果转成业务语言shap的原始输出是数学向量需翻译成业务方能理解的句子。例如对一个booking_status1已预订的用户生成解释“该用户预订概率为 82.3%主要驱动因素提前预订天数lead_time 24 天贡献 0.21提升概率 21%属典型计划型用户历史取消次数previous_cancellations 0贡献 0.15提升 15%表明用户履约意愿强平均房价adr 112 欧元贡献 -0.08降低 8%因价格处于中高端区间略抑制决策。”此报告可直接嵌入 CRM 系统让销售顾问一眼掌握客户画像。从那以后我每次交付模型都强制走一遍shap解释流程——不是为了炫技而是确保当业务方指着某个预测问“为什么”时我能立刻调出这张图而不是翻代码猜逻辑。希望帮到你。本文还有配套的精品资源点击获取
返回列表