- 机器学习
- 教程
【免费下载链接】python-machine-learning-book
The "Python Machine Learning (1st edition)" book code repository and info resource
本指南围绕《Python Machine Learning(第 1 版)》仓库中 第 10 章回归分析 的技术脉络展开,以波士顿房价(Housing)数据集为例,系统讲解从简单线性回归、梯度下降求解、scikit-learn 实现,到 RANSAC 稳健拟合、模型评估、正则化(Lasso)、多项式回归与随机森林回归的完整实战路径。读完本文,你将能独立用numpy/pandas/scikit-learn完成一轮"数据探索 → 建模 → 评估 → 对比调优"的回归任务。全文所有代码与实验输出均可对照仓库中的 ch10.ipynb 与 ch10.py 复现。
本章知识地图
第 10 章的结构(见 README)是一条典型的回归分析流水线,每一环都可独立复用:
- 引入简单线性回归模型
- 探索 Housing 数据集(含可视化特征关系)
- 实现普通最小二乘(OLS)线性回归
- 用梯度下降求解回归参数
- 用 scikit-learn 估计回归系数
- 用 RANSAC 拟合稳健回归模型
- 评估线性回归模型的性能
- 使用正则化方法做回归(Ridge / Lasso)
- 把线性回归变成曲线——多项式回归
- 在 Housing 数据集中建模非线性关系
- 用随机森林处理非线性关系(决策树回归、随机森林回归)
- 小结
下面的小节逐一展开这 8 个环节,并在每处标注仓库中可对照的源码位置。
简单线性回归:从一条直线开始
回归分析要回答的核心问题是:给定一个解释变量(explanatory variable)$x$,如何预测连续的响应变量(response variable)$y$?最简单的模型是假设两者之间存在线性关系:
$$\hat{y} = w_0 + w_1 x$$
其中 $w_0$ 是截距(intercept),$w_1$ 是斜率(slope)。模型学习的目标就是找到一组 $w$,使预测值 $\hat{y}$ 与真实值 $y$ 之间的垂直偏差(vertical offsets)最小化。仓库中的 10_01.png 正是对这一概念的图解:绿色方块代表数据点 $(x_i, y_i)$,蓝色直线代表拟合出的回归线,红色垂直线段刻画每个样本的残差 $|\hat{y}_i - y_i|$。
当只用一个特征(如房间数 RM)去预测房价时,这就是一元线性回归;当引入全部特征时,模型自然推广为多元线性回归 $\hat{y} = w_0 + \sum_{j=1}^{m} w_j x^{(j)}$,这正是第 10 章在 Housing 数据集上最终使用的形态。
探索 Housing 数据集:可视化重要特征
数据来源与字段说明
第 10 章使用的经典 Boston Housing 数据集在仓库中有一份本地副本:datasets/housing/housing.data,配套说明见 datasets/housing/README.md。该数据集共有506 个样本,包含13 个特征和 1 个连续目标变量,特征含义如下:
| 列名 | 含义 |
|---|---|
| CRIM | 城镇人均犯罪率 |
| ZN | 占地超过 25000 平方英尺的住宅用地比例 |
| INDUS | 城镇非零售商业用地比例 |
| CHAS | 是否邻近查尔斯河(哑变量:1 是 / 0 否) |
| NOX | 一氧化氮浓度(每千万分之一) |
| RM | 每套住宅的平均房间数 |
| AGE | 1940 年之前建造的自有住房比例 |
| DIS | 到波士顿 5 个就业中心的加权距离 |
| RAD | 径向高速公路可达性指数 |
| TAX | 每 1 万美元的全额房产税率 |
| PTRATIO | 城镇师生比 |
| B | 1000(Bk - 0.63)²,其中 Bk 为城镇黑人比例 |
| LSTAT | 人口中低收入阶层百分比 |
| MEDV | 目标变量:自住房价格中位数(单位:千美元) |
数据无表头、以空白分隔,因此加载时要手工指定列名(ch10.ipynb 中的做法):
import pandas as pd df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/' 'housing/housing.data', header=None, sep='\s+') df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV'] df.head()如果远程链接不可用,可直接改用仓库本地文件 datasets/housing/housing.data(notebook 中也明确给出了这一替代方案)。
散点矩阵与相关性热力图
在动手建模之前,先用可视化快速判断哪些特征与房价存在(近似线性的)关系。notebook 选取了LSTAT、INDUS、NOX、RM四个特征与目标MEDV,借助 seaborn 画出成对散点图矩阵:
import matplotlib.pyplot as plt import seaborn as sns sns.set(style='whitegrid', context='notebook') cols = ['LSTAT', 'INDUS', 'NOX', 'RM', 'MEDV'] sns.pairplot(df[cols], size=2.5) plt.tight_layout() plt.show()散点矩阵能直观揭示出:RM(平均房间数)与MEDV呈明显正相关,而LSTAT(低收入人口比例)与MEDV呈明显的负相关——这两者都是有潜力的解释变量。随后用热力图定量呈现相关系数矩阵:
import numpy as np cm = np.corrcoef(df[cols].values.T) sns.set(font_scale=1.5) hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 15}, yticklabels=cols, xticklabels=cols) plt.show()这张热力图直接为后续建模提供依据:LSTAT与MEDV相关系数约为-0.74,RM与MEDV约为+0.70,说明两者是预测房价的重要特征;同时INDUS与NOX高度正相关(约 0.76),提示特征间可能存在共线性。
实现普通最小二乘(OLS)线性回归
用梯度下降求解回归参数
scikit-learn 的LinearRegression在底层使用基于最小二乘的闭式求解,但为了理解回归参数的估计原理,第 10 章先用numpy手写了一个批梯度下降版线性回归LinearRegressionGD(源码见 ch10.ipynb):
class LinearRegressionGD(object): def __init__(self, eta=0.001, n_iter=20): self.eta = eta # 学习率 self.n_iter = n_iter # 迭代轮数 def fit(self, X, y): self.w_ = np.zeros(1 + X.shape[1]) self.cost_ = [] for i in range(self.n_iter): output = self.net_input(X) errors = (y - output) self.w_[1:] += self.eta * X.T.dot(errors) self.w_[0] += self.eta * errors.sum() cost = (errors**2).sum() / 2.0 self.cost_.append(cost) return self def net_input(self, X): return np.dot(X, self.w_[1:]) + self.w_[0] def predict(self, X): return self.net_input(X)关键点如下:
- 代价函数采用平方误差和的一半(SSE/2),即 $\frac{1}{2}\sum_i (y_i - \hat{y}_i)^2$,除以 2 是为了让梯度表达更整洁。
- 权重更新对每个权重 $w_j$ 沿负梯度方向前进 $\eta$:
self.w_[1:] += self.eta * X.T.dot(errors)一次完成全部特征的梯度更新;截距单独用errors.sum()更新。 - 标准化是梯度下降的前提:因为不同特征(如 RM 与 TAX)量纲差异巨大,直接训练会导致收敛缓慢甚至震荡。notebook 对输入输出都做了标准化:
from sklearn.preprocessing import StandardScaler sc_x = StandardScaler() sc_y = StandardScaler() X_std = sc_x.fit_transform(X) y_std = sc_y.fit_transform(y[:, np.newaxis]).flatten() lr = LinearRegressionGD() lr.fit(X_std, y_std)训练完成后绘制 "Epoch vs SSE" 曲线,可以确认代价函数随迭代单调下降;打印权重可得标准化空间下的斜率约为 0.695、截距约为 -0.000。预测时需要用sc_y.inverse_transform把标准化的预测值还原回房价量纲(千美元)。notebook 演示了预测 5 间房样本的价格:
num_rooms_std = sc_x.transform(np.array([[5.0]])) price_std = lr.predict(num_rooms_std) print("Price in $1000's: %.3f" % sc_y.inverse_transform(price_std)) # 输出:Price in $1000's: 10.840用 scikit-learn 估计回归系数
手写版适合理解原理,生产实践则直接使用sklearn.linear_model.LinearRegression。它对未标准化的原始数据拟合,直接得到可解释的系数:
from sklearn.linear_model import LinearRegression slr = LinearRegression() slr.fit(X, y) # X = df[['RM']].values, y = df['MEDV'].values y_pred = slr.predict(X) print('Slope: %.3f' % slr.coef_[0]) # Slope: 9.102 print('Intercept: %.3f' % slr.intercept_) # Intercept: -34.671notebook 还演示了**正规方程(Normal Equations)**作为闭式解的另一条路径,结果与LinearRegression完全一致(斜率 9.102、截距 -34.671),验证了库实现的正确性:
# 给设计矩阵加一列全 1 Xb = np.hstack((np.ones((X.shape[0], 1)), X)) w = np.zeros(X.shape[1]) z = np.linalg.inv(np.dot(Xb.T, Xb)) w = np.dot(z, np.dot(Xb.T, y))用 RANSAC 拟合稳健回归模型
数据中难免存在离群点(outlier),普通最小二乘会对离群点过于敏感。RANSAC(Random Sample Consensus,随机抽样一致性)通过"先采样子集拟合、再评估内点/外点"的迭代策略,得到更稳健的模型。notebook 中针对 sklearn 版本做了兼容处理(0.18 前后参数名不同),核心代码如下:
from sklearn.linear_model import RANSACRegressor if Version(sklearn_version) < '0.18': ransac = RANSACRegressor(LinearRegression(), max_trials=100, min_samples=50, residual_metric=lambda x: np.sum(np.abs(x), axis=1), residual_threshold=5.0, random_state=0) else: ransac = RANSACRegressor(LinearRegression(), max_trials=100, min_samples=50, loss='absolute_loss', residual_threshold=5.0, random_state=0) ransac.fit(X, y) inlier_mask = ransac.inlier_mask_ outlier_mask = np.logical_not(inlier_mask)参数含义与取值:
max_trials=100:最多迭代 100 轮随机采样;min_samples=50:每次随机选取至少 50 个样本参与拟合(Housing 共 506 个样本);loss='absolute_loss'/residual_threshold=5.0:以绝对损失作为残差度量,残差绝对值超过 5(千美元)的样本被判为外点;random_state=0:固定随机种子,保证结果可复现。
拟合后可通过inlier_mask_区分内点与外点,并分别用不同颜色和形状绘制(蓝色圆点=内点,浅绿色方块=外点),红色的 RANSAC 拟合线比 OLS 直线更少受外点拖拽。打印最终系数:
print('Slope: %.3f' % ransac.estimator_.coef_[0]) # Slope: 9.621 print('Intercept: %.3f' % ransac.estimator_.intercept_) # Intercept: -37.137对比 OLS 的 9.102 / -34.671 可以发现,RANSAC 的斜率与截距均发生了变化,这正是"稳健拟合"的体现——它受外点影响更小。
评估线性回归模型的性能
评估回归模型不能只看训练集上的表现。notebook 先用train_test_split按30% 测试集、固定random_state=0切分数据(并对 sklearn 0.18 前后的导入路径sklearn.cross_validation/sklearn.model_selection做了兼容),然后用全部 13 个特征训练多元线性回归。
残差图
残差(residual)= 预测值 − 真实值。把残差对预测值作图,可以诊断模型是否满足"残差随机、无系统性模式"的假设:
plt.scatter(y_train_pred, y_train_pred - y_train, c='blue', marker='o', label='Training data') plt.scatter(y_test_pred, y_test_pred - y_test, c='lightgreen', marker='s', label='Test data') plt.xlabel('Predicted values') plt.ylabel('Residuals') plt.legend(loc='upper left') plt.hlines(y=0, xmin=-10, xmax=50, lw=2, color='red') plt.xlim([-10, 50]) plt.show()观察残差图可以发现:预测值较高时残差存在一定的扇形扩散,且部分样本残差偏离明显——这提示房价与特征之间可能存在非线性关系,为后文引入多项式回归与随机森林埋下伏笔。
MSE 与 R²
notebook 用两个经典指标量化性能:
from sklearn.metrics import r2_score from sklearn.metrics import mean_squared_error print('MSE train: %.3f, test: %.3f' % ( mean_squared_error(y_train, y_train_pred), mean_squared_error(y_test, y_test_pred))) print('R^2 train: %.3f, test: %.3f' % ( r2_score(y_train, y_train_pred), r2_score(y_test, y_test_pred)))该模型在 30% 测试集上的结果为:
- MSE(均方误差):训练集 19.958,测试集 27.196;
- R²(决定系数):训练集 0.765,测试集 0.673。
测试集 MSE 明显高于训练集,且 R² 只有 0.67,说明纯线性假设对房价预测并不充分,需要引入正则化或非线性手段。
使用正则化方法:Lasso 回归
正则化通过在损失函数中加入权重惩罚项来抑制过拟合、压缩系数。第 10 章演示了Lasso(L1 正则化,alpha=0.1):
from sklearn.linear_model import Lasso lasso = Lasso(alpha=0.1) lasso.fit(X_train, y_train) y_train_pred = lasso.predict(X_train) y_test_pred = lasso.predict(X_test) print(lasso.coef_)Lasso 的一个重要特性是特征选择:L1 惩罚会把一部分不重要的系数直接压成 0。上面的输出中 13 个系数里就出现了-0.(接近零)的项,这正是稀疏解的表现。性能对比:
- MSE:训练集 20.926,测试集 28.876;
- R²:训练集 0.753,测试集 0.673(此处约为 0.653 量级)。
可以看到 Lasso 以轻微的训练精度损失换来了更稀疏、可解释性更强的模型。读者可以自行调整alpha观察正则强度对系数稀疏度与测试集表现的影响(Ridge 即 L2 版本同理,仓库中未展开但接口一致)。
把线性回归变成曲线:多项式回归
多项式特征与玩具示例
线性回归的"线性"指的是对参数$w$ 线性,而非对特征 $x$ 线性。借助PolynomialFeatures构造 $x^2, x^3, \dots$ 等衍生特征,就能用线性模型拟合曲线关系。notebook 先用一组包含 10 个点的玩具数据演示了线性拟合与二次拟合的差异:
from sklearn.preprocessing import PolynomialFeatures lr = LinearRegression() pr = LinearRegression() quadratic = PolynomialFeatures(degree=2) X_quad = quadratic.fit_transform(X)拟合后对比两者在训练数据上的表现:
- 线性拟合 MSE:569.780,R²:0.832;
- 二次拟合 MSE:61.330,R²:0.982。
仅把次数从 1 提到 2,MSE 就下降了近一个数量级,R² 从 0.832 跃升到 0.982——直观说明二次曲线能更好地贴合该玩具数据。
在 Housing 数据集中建模非线性关系
回到波士顿房价数据,用LSTAT单一特征分别做线性(d=1)、二次(d=2)、三次(d=3)多项式拟合:
X = df[['LSTAT']].values y = df['MEDV'].values quadratic = PolynomialFeatures(degree=2) cubic = PolynomialFeatures(degree=3) X_quad = quadratic.fit_transform(X) X_cubic = cubic.fit_transform(X) X_fit = np.arange(X.min(), X.max(), 1)[:, np.newaxis] regr = regr.fit(X, y) y_lin_fit = regr.predict(X_fit) linear_r2 = r2_score(y, regr.predict(X)) regr = regr.fit(X_quad, y) y_quad_fit = regr.predict(quadratic.fit_transform(X_fit)) quadratic_r2 = r2_score(y, regr.predict(X_quad)) regr = regr.fit(X_cubic, y) y_cubic_fit = regr.predict(cubic.fit_transform(X_fit)) cubic_r2 = r2_score(y, regr.predict(X_cubic))图中三条曲线(蓝色点线=d=1、红色实线=d=2、绿色虚线=d=3)叠加在灰色训练点上,LSTAT与MEDV的负相关呈现出明显的非线性下凹形态,多项式拟合显著优于单一直线。
特征变换:log 与平方根
除了构造多项式,还可以对原始特征做单调变换。notebook 的另一条思路是对LSTAT取对数、对MEDV取平方根,再在变换后的空间做线性拟合:
X_log = np.log(X) y_sqrt = np.sqrt(y) regr = regr.fit(X_log, y_sqrt) y_lin_fit = regr.predict(X_fit) linear_r2 = r2_score(y_sqrt, regr.predict(X_log))变换后 $log(LSTAT)$ 与 $\sqrt{MEDV}$ 呈现出更接近直线的分布,变换空间中的线性模型即可获得较好的 R²。这种"先变换、再线性拟合"的思路是处理偏态分布与非线性关系的常用工程手段。
用随机森林处理非线性关系
树模型天然支持非线性与特征交互,无需手工构造多项式特征,是多项式回归之外的另一种选择。
决策树回归
DecisionTreeRegressor通过递归划分特征空间、在每个叶节点取均值的方式做回归。notebook 用max_depth=3限制树深防止过拟合:
from sklearn.tree import DecisionTreeRegressor X = df[['LSTAT']].values y = df['MEDV'].values tree = DecisionTreeRegressor(max_depth=3) tree.fit(X, y) sort_idx = X.flatten().argsort() lin_regplot(X[sort_idx], y[sort_idx], tree) plt.xlabel('% lower status of the population [LSTAT]') plt.ylabel('Price in $1000\'s [MEDV]') plt.show()注意:由于决策树预测是分段常数,必须先按X排序再绘图,否则连线会杂乱无章。max_depth=3让拟合线呈现为 2³=8 段以内的阶梯状,既能捕捉非线性趋势,又避免了高方差过拟合。仓库中另附有 tree.dot,可用于将决策树导出为 Graphviz 可视化,直观查看切分规则。
随机森林回归
随机森林通过自助采样(bootstrap)+ 特征随机子集 + 多棵树平均来降低单棵决策树的高方差。notebook 用 40% 测试集评估了 1000 棵树的随机森林:
from sklearn.ensemble import RandomForestRegressor forest = RandomForestRegressor(n_estimators=1000, criterion='mse', random_state=1, n_jobs=-1) forest.fit(X_train, y_train) y_train_pred = forest.predict(X_train) y_test_pred = forest.predict(X_test) print('MSE train: %.3f, test: %.3f' % ( mean_squared_error(y_train, y_train_pred), mean_squared_error(y_test, y_test_pred))) print('R^2 train: %.3f, test: %.3f' % ( r2_score(y_train, y_train_pred), r2_score(y_test, y_test_pred)))参数说明:
n_estimators=1000:森林中决策树的数量,越多通常越稳健(配合并行可控制耗时);criterion='mse':节点划分依据为均方误差;random_state=1:固定随机种子保证可复现;n_jobs=-1:使用全部 CPU 核并行训练。
在全部 13 个特征上的结果:
- MSE:训练集 1.642,测试集 11.052;
- R²:训练集 0.979,测试集 0.878。
与线性回归(测试 R²≈0.67)、Lasso(测试 R²≈0.65)相比,随机森林在测试集上把 R² 提升到了约0.88。训练集 R² 高达 0.979 而测试集为 0.878,二者仍有差距,说明随机森林在训练集上存在一定过拟合,但泛化能力已显著优于线性族模型。残差图也证实了这一点:训练残差被压缩到接近零,测试残差集中在零线附近、分布更对称。
小结:如何选模型
沿着第 10 章的完整流水线走下来,可以总结出一条清晰的选型逻辑:
- 先探索:用散点矩阵与相关性热力图(10_04.png)识别与目标强相关的特征及潜在非线性(如
LSTAT、RM); - 从简单模型起步:手写梯度下降版
LinearRegressionGD理解参数估计原理,再用 scikit-learnLinearRegression与正规方程做基准; - 考虑数据质量:存在离群点时改用 RANSAC 稳健拟合;
- 严格评估:用 30%~40% 测试集 + MSE / R² / 残差图衡量泛化能力,而不是只看训练集;
- 对症下药:
- 特征多、要稀疏可解释 → Lasso(
alpha=0.1); - 特征与目标呈曲线关系 →
PolynomialFeatures(d=2、d=3)或 log/平方根变换; - 关系复杂、树模型泛化更好 →
DecisionTreeRegressor(max_depth=3)起步,再上RandomForestRegressor(n_estimators=1000)。
- 特征多、要稀疏可解释 → Lasso(
仓库中的 ch10.ipynb 与 ch10.py 完整收录了以上全部代码与输出,datasets/housing/README.md 提供了数据集字段的权威说明,可随时对照复现。建议读者在跑通后自行调节eta、alpha、max_depth、n_estimators等关键超参,观察它们如何影响 SSE 收敛曲线、系数稀疏度与测试集 R²,从而把本章的方法内化为自己的回归建模工具箱。
- 机器学习
- 教程
【免费下载链接】python-machine-learning-book
The "Python Machine Learning (1st edition)" book code repository and info resource
相关推荐
ReplaceAnimation:如何在iOS中实现惊艳的下拉刷新动画效果
ReplaceAnimation:如何在iOS中实现惊艳的下拉刷新动画效果 ReplaceAnimation是一个基于Swift编写的iOS开源项目,专注于在U
PakePlus-Android:原生WebView容器化架构与极简打包技术解析
PakePlus Android:原生WebView容器化架构与极简打包技术解析 PakePlus Android是一款基于原生Android WebView容
移动开发从感知机到 Adaline:用 Python 从零实现线性分类器(python-machine-learning-book 第 2 章实战解析)
从感知机到 Adaline:用 Python 从零实现线性分类器(python machine learning book 第 2 章实战解析) 本文围绕 py
机器学习教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考