简介:面向机器学习初学者与实践者的分类与回归实战项目包,聚焦监督学习两大核心任务。资源以波士顿房价预测为回归案例,系统演示线性回归、岭回归等算法从数据预处理、特征选择到模型训练、交叉验证与性能评估的完整流程;同时配套酒店数据集分类项目,涵盖逻辑回归、决策树等常用算法对比,便于体会分类与回归在问题定义、模型选择及评价指标上的差异。压缩包共7个文件,包括2个可运行的Jupyter Notebook、2个CSV数据集、2个HTML结果预览和1份项目说明文档,整体大小2.23MB。Notebook 内含代码与输出结果,可直接修改运行,HTML 文件可快速浏览分析过程,docx 文档补充项目背景与操作说明。已有986人学习下载,既能满足课程实验、毕业设计参考,也可作为入门机器学习的动手训练素材。
1. 机器学习实战项目:分类与回归,两份数据集把监督学习的底子一次打牢
做机器学习这行最怕的不是模型跑不出来,而是拿着数据不知道从哪里下手。这份「机器学习实战项目——分类&回归.zip」解决的就是这个问题:它用波士顿房价回归和 INN Hotels Group 酒店预订分类两个真实数据集,把监督学习里最核心的两类任务完整走了一遍。压缩包里不是散落的代码,而是一套能跑通的 Notebook、数据集和项目说明文档,适合刚学完机器学习理论、想动手验证的初学者,也适合需要快速回顾完整建模流程的从业者。数据预处理、特征选择、模型训练、评估指标这些环节,在这两个项目里都能找到对应的实际操作,不是空讲概念。
2. 拆开压缩包:先看清两个任务分别是什么、用什么模型、怎么评估
2.1 文件构成与用途对照
拿到压缩包后,第一件事不是急着跑代码,而是先把文件清单过一遍,搞清楚每个文件是干什么的。这个习惯能避免后面拿错数据集、看错 Notebook 的低级错误。
| 文件 | 类型 | 用途 |
|---|---|---|
| Boston.csv | 数据集 | 波士顿房价回归实验的数据,包含 14 个特征列和 1 个目标列 |
| Learners_Notebook_Boston_house_price.ipynb | Notebook | 波士顿房价回归的完整建模代码,从导入数据到模型评估 |
| Regression.html | 文档 | 回归项目 Notebook 的静态导出版,没装 Jupyter 也能直接看结果 |
| Project Description - Machine Learning.docx | 文档 | 项目说明,讲清了两个任务的背景、目标和评估要求 |
| Classification.html | 文档 | 分类项目 Notebook 的静态导出版 |
| INNHotelsGroup.csv | 数据集 | INN Hotels Group 酒店预订数据,用于分类建模 |
| Learner+Notebook+-+Project_Classification_ML.ipynb | Notebook | 分类任务的完整代码,包含数据检查和建模过程 |
回归的 Notebook 我一般直接拿 Jupyter Notebook 打开,HTML 版本是给不想装环境的人预览用的。两个 CSV 数据集需要用 pandas 读取,做过数据处理的人对这个流程不会陌生。
2.2 分类与回归:任务定义上的本质差别
分类和回归同属监督学习,但目标变量类型完全不同。回归预测的是连续数值,比如波士顿房价数据集里的 MEDV(中位房价),它是一个连续的浮点数,模型输出的是一个数值。分类预测的是离散类别,比如 INN Hotels Group 数据里判断客人是否会取消预订,输出是 0 或 1,属于二分类问题。
这个区别直接决定了评估指标的选择。回归任务看 MSE、RMSE、R²,分类任务看准确率、精确率、召回率和 F1。很多初学者在这两个任务之间切换时,容易把评估指标混着用,比如拿准确率去评价回归模型,这没有任何意义。项目里两个 Notebook 是分开写的,正好体现了这种差异,做第一遍的时候建议按顺序先回归后分类,理解会顺畅很多。
2.3 两套数据集的字段与目标变量说明
波士顿房价数据集来自 1978 年的波士顿郊区,字段包括犯罪率(CRIM)、住宅用地比例(ZN)、非零售商业用地比例(INDUS)、查尔斯河虚拟变量(CHAS)、一氧化氮浓度(NOX)、平均房间数(RM)、房龄(AGE)等 13 个特征,目标变量是 MEDV(中位房价,单位千美元)。这个数据集经典在于特征之间有明显的共线性,比如 DIS(到就业中心距离)和 NOX(一氧化氮浓度)往往负相关,这种结构适合拿来练习正则化模型,观察 Lasso 和 Ridge 如何处理共线性。
INN Hotels Group 数据集字段包括预订类型、入住日期、取消标志等,最核心的目标列是取消标志(通常为 0 或 1)。这个数据的价值在于类别通常不平衡,酒店预订里取消的订单比例往往只有两三成,这意味着不能只看准确率,要看召回率和 F1。
3. 波士顿房价回归:完整跑通线性回归到正则化模型的链路
3.1 数据清洗与特征检查
拿到 Boston.csv 后,第一步永远是数据体检。用 pandas 读进来,先看形状、缺失值、数据类型,再对目标变量做分布检查。波士顿房价数据集本身比较干净,但练习时故意制造缺失值、添加异常值也是常见做法,目的是练熟处理套路。
import pandas as pd import numpy as np # 读取波士顿房价数据 boston = pd.read_csv('Boston.csv') # 查看数据形状和列名 print(boston.shape) print(boston.columns.tolist()) # 检查缺失值 print(boston.isnull().sum()) # 目标变量分布检查 import matplotlib.pyplot as plt plt.hist(boston['MEDV'], bins=30, edgecolor='black') plt.xlabel('MEDV (千美元)') plt.ylabel('频数') plt.show()这段代码完成三件事:确认数据规模、检查缺失值、观察目标变量分布。MEDV 的分布如果不是标准的正态分布,后续做线性回归时可能需要对目标做对数变换。波士顿房价数据里存在少数 MEDV 被截断到 50 的情况,直方图会在右侧出现一个尖峰,这是 1978 年数据采集时的上限截断,属于数据集本身的结构特点,建模时要么保留,要么删除截断样本,看你要解决什么问题。
3.2 特征相关性分析与数据集划分
建模前需要看特征与目标的相关系数,这是特征选择的依据。波士顿房价数据里有几个特征与 MEDV 的相关性非常明显,比如 RM(平均房间数)正相关最强,LSTAT(低收入人口比例)负相关明显。
# 计算特征与目标变量的相关系数 corr = boston.corr()['MEDV'].sort_values(ascending=False) print(corr) # 划分训练集和测试集 from sklearn.model_selection import train_test_split X = boston.drop('MEDV', axis=1) y = boston['MEDV'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) print(f'训练集规模: {X_train.shape}, 测试集规模: {X_test.shape}')参数说明:test_size=0.2表示留出 20% 的数据做测试,random_state=42固定随机种子,保证每次运行划分结果一致,方便复现实验。训练集规模约 404 条,测试集约 101 条,样本量不算大,后面调参时交叉验证比单次划分更稳。
做特征选择时,我一般不会只凭相关系数删列,而是结合领域知识和模型反馈。波士顿数据里 DIS 和 NOX 有强共线性,两个都放进去会让线性回归的系数估计变得不稳定,但岭回归可以缓解这类问题。相关系数矩阵可以用可视化进一步确认,不过新手阶段看排序结果就够了。
3.3 线性回归、岭回归与 Lasso:对比它们的实际表现
波士顿房价最经典的做法是用线性回归作为基线,然后对比岭回归(Ridge)和 Lasso。Ridge 适合处理特征共线性,Lasso 能做特征选择。三者用 sklearn 调用非常简单,但要注意:线性回归需要先标准化特征,否则量纲差异大会影响系数解释和正则化效果。
from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression, Ridge, Lasso from sklearn.metrics import mean_squared_error, r2_score # 特征标准化 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 线性回归 lr = LinearRegression() lr.fit(X_train_scaled, y_train) y_pred_lr = lr.predict(X_test_scaled) # 岭回归 ridge = Ridge(alpha=1.0) ridge.fit(X_train_scaled, y_train) y_pred_ridge = ridge.predict(X_test_scaled) # Lasso lasso = Lasso(alpha=0.1) lasso.fit(X_train_scaled, y_train) y_pred_lasso = lasso.predict(X_test_scaled) # 评估 for name, y_pred in [('LinearRegression', y_pred_lr), ('Ridge', y_pred_ridge), ('Lasso', y_pred_lasso)]: mse = mean_squared_error(y_test, y_pred) rmse = np.sqrt(mse) r2 = r2_score(y_test, y_pred) print(f'{name}: RMSE={rmse:.2f}, R2={r2:.3f}')这段代码展示了三个模型的训练和评估。StandardScaler的fit_transform和transform分开调用,是因为测试集用的是训练集的均值和标准差,不能重新计算,这是个高频出错点。Ridge 的alpha=1.0控制正则化强度,alpha 越大系数压缩越狠;Lasso 的alpha=0.1做的是 L1 惩罚,会把不重要特征的系数压到 0,天然做特征选择。
三个模型跑完后,对比 RMSE 和 R² 的差别。常见结果有两种:一是三个模型表现接近,说明线性假设基本成立;二是 Ridge 比线性回归好,说明数据存在共线性。这时候要看 Lasso 的系数,哪种特征被压成了 0,就知道哪些变量对房价预测贡献不大。
3.4 残差分析:判断模型是否漏掉了非线性关系
评估回归模型不能只看 R²,还要画残差图。残差是 y_test 减去 y_pred,如果残差随机分布在 0 值附近,说明模型拟合合理;如果呈现明显的曲线形状,说明存在非线性关系,线性模型没有捕捉到。
# 计算残差 residuals = y_test - y_pred_lr # 画残差图 plt.scatter(y_pred_lr, residuals, alpha=0.6) plt.axhline(y=0, color='red', linestyle='--') plt.xlabel('预测值') plt.ylabel('残差') plt.title('线性回归残差图') plt.show()残差图中如果出现漏斗形状,即随着预测值增大残差方差也在增大,说明存在异方差性,这时可以考虑对目标变量做对数变换。波士顿房价截断到 50 的样本会让残差图右侧出现一条竖线,这就是数据结构的痕迹。遇到这种情况,我的做法是把残差分析结果写进实验笔记,作为模型局限性的说明,而不是急着换模型。
4. INN Hotels Group 分类:把预订取消预测做成一个可落地的二分类模型
4.1 从业务问题到分类任务:目标列怎么定义
INN Hotels Group 数据集做的是酒店预订取消预测,核心问题是一个客人订了房间之后会不会取消。这是一个典型的二分类问题:目标列为是否取消,取值为 0(未取消)或 1(取消)。
处理这种数据,第一步是看类别分布。
import pandas as pd # 读取酒店数据 hotels = pd.read_csv('INNHotelsGroup.csv') # 查看目标列分布 cancel_counts = hotels['IsCanceled'].value_counts(normalize=True) print(cancel_counts) # 查看缺失值 print(hotels.isnull().sum()) # 查看数据类型 print(hotels.dtypes.value_counts())如果取消类别占比低于 30%,就是不平衡数据集,直接建模会出问题。酒店取消场景里这很常见,反反复复都是如此。value_counts(normalize=True)返回占比,方便一眼看出不平衡程度。
另外,数据里的类型分布值得注意:有些列是数值型,有些是类别型,类别型特征需要做编码。常见的做法是 LabelEncoder 处理有序类别,OneHotEncoder 处理无序类别。酒店数据里的房间类型、预订渠道这些字段,都属于无序类别,用独热编码。
4.2 特征工程:日期字段拆解与数值特征标准化
酒店预订数据的特征工程,核心是挖掘日期字段。入住日期、预订日期这两个字段,如果直接用原始格式喂给模型是不行的,需要拆成年月日、星期几、提前预订天数等。这些衍生特征往往比原始特征更有预测力。
from sklearn.preprocessing import StandardScaler, OneHotEncoder import pandas as pd # 假设数据里有预订日期 ArrivalDate 和入住日期 # 这里以常见的日期拆解为例 hotels['BookingDate'] = pd.to_datetime(hotels['BookingDate']) hotels['ArrivalDate'] = pd.to_datetime(hotels['ArrivalDate']) # 提前预订天数 hotels['LeadTime'] = (hotels['ArrivalDate'] - hotels['BookingDate']).dt.days # 入住月份和星期几 hotels['ArrivalMonth'] = hotels['ArrivalDate'].dt.month hotels['ArrivalDayOfWeek'] = hotels['ArrivalDate'].dt.dayofweek # 数值特征标准化 num_cols = ['LeadTime', 'ArrivalMonth', 'ArrivalDayOfWeek'] scaler = StandardScaler() hotels_scaled = scaler.fit_transform(hotels[num_cols])LeadTime是提前预订天数,这个特征在酒店取消预测里非常关键,一般预订时间越早,取消概率越高。ArrivalMonth捕获季节性,夏季和节假日的取消行为差别很大。ArrivalDayOfWeek看周末和工作日的差异。日期拆解本身是个熟能生巧的活,不同业务场景拆出来的特征也不同,但这一套组合在酒店场景里是通用的。
类别特征的独热编码需要特别小心,它会把一列变成多列,训练集和测试集必须用同一个编码器。我后面避坑章会专门讲这个问题,这里先记住一个原则:fit只能在训练集上调用一次。
4.3 基线模型与类别不平衡处理
分类问题不要一上来就上复杂模型,先跑一个逻辑回归作为基线,让心中有底。但酒店取消数据类别不平衡时,直接跑逻辑回归会被多数类主导,预测结果偏向把所有样本判为 0。这时候需要做两步处理:调整类别权重,换评估指标。
from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, confusion_matrix # 准备特征和目标 X_h = hotels.drop('IsCanceled', axis=1) y_h = hotels['IsCanceled'] X_train_h, X_test_h, y_train_h, y_test_h = train_test_split( X_h, y_h, test_size=0.2, random_state=42, stratify=y_h ) # 使用类别权重平衡 log_reg = LogisticRegression(class_weight='balanced', max_iter=1000) log_reg.fit(X_train_h, y_train_h) y_pred_h = log_reg.predict(X_test_h) # 输出分类报告 print(classification_report(y_test_h, y_pred_h))class_weight='balanced'让 sklearn 根据类别频率自动调整权重,少数类的错分代价提高,模型不会一味偏向多数类。stratify=y_h保证训练集和测试集里取消与未取消的比例和原始数据一致,这点在不平衡数据集上必须做,否则随机划分可能导致测试集里取消样本极少,评估失真。
max_iter=1000是因为特征经过独热编码后维度增加,逻辑回归默认迭代次数可能不够收敛,报 warning 时可以这样调。classification_report 里要重点看少数类(类别 1)的精确率、召回率和 F1,这三个值比准确率诚实得多。
4.4 随机森林上场:对比逻辑回归在非线性特征上的差距
逻辑回归是线性模型,如果特征与目标之间存在非线性关系,它的表达能力有限。随机森林能捕捉非线性交互,而且天然处理类别特征(虽然编码后效果更好)。作为第二个模型,随机森林主要用于对比上限。
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器 rf = RandomForestClassifier( n_estimators=100, max_depth=8, random_state=42, class_weight='balanced' ) rf.fit(X_train_h, y_train_h) y_pred_rf = rf.predict(X_test_h) print(classification_report(y_test_h, y_pred_rf)) print(confusion_matrix(y_test_h, y_pred_rf)) # 特征重要性排序 feature_importance = pd.Series( rf.feature_importances_, index=X_train_h.columns ).sort_values(ascending=False) print(feature_importance.head(10))n_estimators=100控制树的棵数,一般 100 到 200 之间足够。max_depth=8限制单棵树深度,防止过拟合。特征重要性输出能看到 LeadTime 是否排在前面,这会验证之前做特征工程时对提前预订天数的判断。如果随机森林的 F1 比逻辑回归高不少,说明数据里确实有非线性关系;如果差不多,线性模型就够了,复杂模型反而增加部署成本。
这个判断直接影响后续方向:是深入调随机森林的参数,还是转向 XGBoost、LightGBM,或者干脆用逻辑回归上线。机器学习项目里的模型选择,本质上是收益和成本的权衡。
5. 实战避坑:分类与回归项目里我踩过的六个坑
5.1 数据泄漏:把目标变量的衍生信息当成了特征
现象:训练时 R² 高达 0.95,测试集表现也很好,但主观上一看特征列表就发现不对——某个特征本质上和目标值有直接计算关系。
原因:数据预处理时不小心把包含目标信息的列留在了特征里。波士顿房价数据集里没有这个问题,但自建特征时很容易犯。比如把 MEDV 的均值或某个分桶结果作为特征加进去,模型直接从特征里读答案。
解决:建模前用X.columns核对特征列表,确保没有目标变量或目标变量的直接变换列。另外可以在数据清洗阶段把明显冗余的列剔除,比如 ID 列、和目标完全重复的列。做特征工程时多留个心眼,加特征前问自己:这个特征是否包含了答案?
5.2 标准化时把缩放器在训练集和测试集上分别 fit 了
现象:模型训练正常,但测试集预测结果离谱,数值波动巨大。
原因:训练时对 X_train 做了scaler.fit_transform,测试时对 X_test 又单独做了一次scaler.fit_transform,导致两个数据集的均值和标准差不同,分布完全错位。新手经常因为图省事,把两行代码写成一样的。
解决:统一用训练集的缩放器。训练集上fit_transform,测试集上只调用transform。这个坑在回归和分类项目里都会出现,一旦犯过,后续再做数据预处理时我会强制自己检查这两行代码是不是同一个对象。
5.3 类别不平衡时硬套 accuracy,模型看似很准实则没用
现象:酒店取消数据里 80% 的订单未取消,模型全部预测为 0,准确率直接 80%,看起来不错,但对取消订单的召回率是 0,业务上完全不可用。
原因:accuracy 在类别不平衡场景下会被多数类主导。分类项目里如果只看准确率,很容易产出一个实际没有价值的模型。
解决:换成 precision、recall、F1 和 confusion matrix。分类项目 Notebook 里应该输出 classification_report 而不是只报 accuracy。对于酒店取消预测,通常更关心召回率——漏掉一个取消订单意味着房间空置,成本很高。
5.4 独热编码后训练集和测试集列数不一致
现象:模型训练完,预测时报错,提示测试集的特征数量与训练集不一致。
原因:训练集里某个类别出现的值,测试集里没有出现,或者相反。独热编码器在训练集上 fit 后固定了列名集合,测试集经过同样的编码器处理时,如果出现了训练集中没见过的类别,会报错或生成新的列。
解决:OneHotEncoder 设置handle_unknown='ignore',它在遇到未知类别时输出全零列而不是报错。另外建议用一个 ColumnTransformer 统一处理数值和类别特征,它能保证完整的数据处理流程作为同一个 pipeline 被复用,训练和推理时不会出现列错位。
5.5 交叉验证和测试集混在一起,调参调出了虚假分数
现象:调参时交叉验证分数一直在涨,感觉模型越调越好,最后在测试集上一跑,分数远低于预期。
原因:调试过程中反复用同一个测试集评估效果,要么是random_state没固定,导致每次划分的训练集和测试集不同;要么是拿测试集参与了调参,测试集已经不再“未见”,泛化能力被高估。
解决:先把测试集锁死,交叉验证只用在训练集内部。调参时用 GridSearchCV 加cv=5,找到最优参数后在单独的测试集上做一次最终验证。从那以后我每次跑实验都会用版本号记录数据划分方式和随机种子,调参变量再多也不会自己骗自己。
6. 一个值得养成的习惯:训练前先把基线跑出来,再谈调参
动手做分类或回归项目之前,我强烈建议先跑一个最简单的基线模型,再用结果调整后续策略。两个项目里最直接的基线分别是线性回归和逻辑回归,先把它们跑通,得到一组原始分数,后面的模型才有比较对象。
以波士顿房价为例,基线模型跑出来 R² 如果是 0.7 左右,这说明线性假设对当前数据基本适用。如果 R² 只有 0.4,那第一个要查的就不是调参,而是数据有没有预处理问题、特征是不是选错了。模型调优有个铁律:先保证数据对,再调参数。
具体操作上,我一般会用 sklearn 的Pipeline把标准化、特征编码、模型训练串成一个完整流程,这样既省去了反复写代码的麻烦,也避免漏掉某一步处理。
from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder # 数值列和类别列分开处理 num_features = ['LeadTime', 'ArrivalMonth', 'ArrivalDayOfWeek'] cat_features = ['RoomType', 'MarketSegment'] preprocessor = ColumnTransformer( transformers=[ ('num', StandardScaler(), num_features), ('cat', OneHotEncoder(handle_unknown='ignore'), cat_features) ] ) # 把预处理和模型串成 pipeline pipeline = Pipeline(steps=[ ('preprocessor', preprocessor), ('classifier', RandomForestClassifier( n_estimators=100, max_depth=8, class_weight='balanced', random_state=42 )) ]) # 直接用 pipeline 训练和预测 pipeline.fit(X_train_h, y_train_h) y_pred = pipeline.predict(X_test_h)Pipeline 的价值在于把整个处理链锁在一个对象里。在线预测时不用手动记住标准化、编码、模型这三步的操作顺序,一个pipeline对象从头到尾搞定,也不会有中间步骤被遗忘的风险。测试集验证时,数据泄漏的可能性被降到最低。
先用逻辑回归拿基线,然后用 Pipeline 把随机森林串起来,比较两者的 F1 分数和混淆矩阵,确认复杂模型带来的收益是否值得。如果随机森林比逻辑回归的 F1 只提升了不到两个百分点,我会选择逻辑回归——部署简单,解释性强,业务方也容易接受。机器学习不是越复杂的模型越好,是在给定资源条件下找到最合用的方案。这个习惯帮我少走了很多弯路,希望帮到你。
本文还有配套的精品资源,点击获取