ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

简单线性回归入门:最小二乘法与Python实战全解析

2026/9/8 6:12:54 拓冰建站 浏览量
简单线性回归入门:最小二乘法与Python实战全解析 还记得第一次接触机器学习的时候很多人上来就甩一堆神经网络、决策树、SVM 的名词结果听得云里雾里。其实机器学习并没有那么玄乎几乎所有教材都会把简单线性回归放在第一课因为它是最直观、最容易理解、也最能帮你建立“模型思维”的算法。本文就用最通俗的语言把简单线性回归的直觉、数学原理、Python 代码和落地踩坑一次讲清楚。不管你是刚准备入门机器学习的新手还是已经会调 sklearn 但想搞懂背后原理的开发者这篇文章都值得收藏。我们不仅会讲“怎么调库”还会用 NumPy 从零手写一遍回归参数求解过程让你真正理解最小二乘法和梯度下降是怎么回事。1. 简单线性回归先建立直觉1.1 一个最生活化的例子房价预测假设你是一个刚入行的房产中介想根据房屋面积来预估房价。你手头有过去一段时间的历史成交数据比如 50 平米卖了 80 万80 平米卖了 120 万100 平米卖了 150 万。现在来了一套 90 平米的房子你应该报多少万合适如果你的第一反应是“画一条线让这条线尽量穿过这些点然后看 90 平米在这条线上的位置”——恭喜你你已经具备了简单线性回归的核心直觉。简单线性回归要做的就是找到一条直线使得所有真实样本点到这条直线之间的“距离”最小。有了这条线你就能对不在数据集里的新样本做预测。1.2 数学模型一条直线怎么描述从数学上看简单线性回归的形式非常简单y wx bx特征值也就是输入变量比如房屋面积。y目标值也就是我们要预测的结果比如房价。w权重斜率表示 x 每变化一个单位y 平均变化多少。b偏置截距表示 x 0 时的 y 值。这里“简单”两个字指的就是只有一个特征 x。如果特征有两个以上就变成了多元线性回归那是以后的事。在机器学习术语里我们通常把 x 叫做特征、自变量把 y 叫做标签、目标值或因变量。模型训练的过程本质上就是在找一个合适的 w 和 b。1.3 损失函数怎样才算“拟合得好”有了直线怎么衡量它好不好我们需要一个量化指标。最常见的做法是计算残差真实值 y 减去预测值 ŷ。为了避免正负抵消也不希望用绝对值导致数学上不好求导我们通常用残差的平方和也就是损失函数 J(w, b) (1 / n) * Σ(yi - (wxi b))²这个损失函数也叫均方误差Mean Squared Error, MSE。我们的目标非常明确找到一组 w 和 b让这个损失函数的值尽可能小。这里有一个新手容易混淆的点损失函数里面的 xi 和 yi 是已知的训练数据真正要优化的是 w 和 b。x 和 y 是固定的“考题”w 和 b 是我们要填的“答案”。1.4 容易踩的误区相关关系不等于因果关系很多初学者看到“预测”两个字就容易联想到因果。这里要特别强调一下回归分析发现的是相关性不是因果关系。比如我们可能会发现“冰淇淋销量”和“溺水人数”有很强的正相关如果你用简单线性回归去建模模型确实能拟合得很好。但你不能说“冰淇淋卖得越多溺水的人越多”真正的幕后推手是天气变热。这一点在工程落地上非常重要模型预测准确不代表你理解了业务机制。解释模型时要谨慎使用“因为……所以……”的说法。2. 环境准备与示例数据2.1 运行环境说明本文示例代码以 Python 为例使用的核心库是 NumPy 和 scikit-learn绘图使用 Matplotlib。版本不需要严格固定以下是一个常见的组合Python 3.8 或更高版本NumPy 1.21 及以上scikit-learn 1.0 及以上Matplotlib 3.5 及以上如果你使用 Anaconda通常这些库已经内置。如果还没安装可以在终端执行pip install numpy scikit-learn matplotlib2.2 准备一份简单数据为了演示方便我们不使用真实产业数据而是手动构造一组带有线性规律的数据。这样做的优点是你自己知道真实的 w 和 b 是多少方便验证模型学到的参数是否正确。import numpy as np # 设置随机种子保证实验结果可复现 np.random.seed(42) # 生成 100 个样本特征 x 在 0 到 10 之间均匀分布 X np.linspace(0, 10, 100) # 真实的权重和偏置 true_w 2.5 true_b 1.0 # y 2.5x 1 噪声 y true_w * X true_b np.random.randn(100) * 2.0这里的np.random.randn(100) * 2.0是给数据添加一些随机噪声。为什么要加噪声因为现实世界的数据几乎不可能完美落在一条直线上总会受各种因素干扰。加噪声能让我们的示例更接近真实场景。3. 求解参数两条核心路线有了数据和损失函数接下来重点来了怎么找到最优的 w 和 b这里有两个经典方案最小二乘法和梯度下降法。3.1 最小二乘法正规方程最小二乘法的核心思想是既然损失函数是一个关于 w 和 b 的凸函数那我们直接对 w 和 b 求偏导令偏导等于 0就能解出最优解。推导过程这里不展开直接给出结论。对于简单线性回归w Σ((xi - x̄)(yi - ȳ)) / Σ((xi - x̄)²) b ȳ - w * x̄其中 x̄ 和 ȳ 分别是 x 和 y 的均值。从公式可以看出最小二乘法非常直观斜率 w 刻画的是 x 和 y 的协动程度除以 x 自身的变异程度。这是一种纯数学解法不需要迭代一次算完。3.2 梯度下降法梯度下降的思路则完全不同。它不试图直接解方程而是模拟一个“下山”的过程。想象你站在山顶周围一片漆黑但你希望能走到山谷最低点。你能做的只有“摸一摸脚下的坡度”然后朝坡度下降最快的方向迈一步反复执行直到感觉脚下已经足够平坦。在数学上“脚下的坡度”就是损失函数对 w 和 b 的偏导数。更新公式如下w w - 学习率 * (2 / n) * Σ((wxi b - yi) * xi) b b - 学习率 * (2 / n) * Σ(wxi b - yi)这里引入了一个新概念学习率learning rate。它决定了你每一步迈多大。学习率太大容易一步迈过头在最低点附近来回震荡学习率太小训练速度又会非常慢。3.3 两种方法的对比对比维度最小二乘法梯度下降法计算方式直接求解一步到位迭代逼近逐步优化计算成本需要计算均值、协方差大数据集较慢每轮都要遍历数据但可以配合随机优化适用场景特征少、数据量小的简单场景特征多、数据量大、无法直接求逆的场景实现难度简单稍复杂需要调学习率是否需要特征缩放一般不需要特征尺度差异大时强烈建议这篇教程会重点演示第一种的代码实现同时也会给出梯度下降的简洁实现方便你对比理解。4. 完整代码实战4.1 从零实现最小二乘法下面我们不借助 sklearn直接用 NumPy 手写线性回归参数求解。这样能最大程度看清算法内部发生了什么。import numpy as np import matplotlib.pyplot as plt # 1. 生成示例数据 np.random.seed(42) X np.linspace(0, 10, 100) true_w 2.5 true_b 1.0 y true_w * X true_b np.random.randn(100) * 2.0 # 2. 最小二乘法求解 def simple_linear_regression(X, y): 简单线性回归的最小二乘解 返回: (w, b) x_mean np.mean(X) y_mean np.mean(y) # 按照公式计算 w numerator np.sum((X - x_mean) * (y - y_mean)) denominator np.sum((X - x_mean) ** 2) w numerator / denominator # 根据 w 计算 b b y_mean - w * x_mean return w, b w, b simple_linear_regression(X, y) print(f最小二乘法求解结果: w {w:.4f}, b {b:.4f}) print(f真实生成数据时: w {true_w}, b {true_b})运行这段代码你会看到类似这样的输出最小二乘法求解结果: w 2.4733, b 1.2185 真实生成数据时: w 2.5, b 1.0由于我们手动添加了随机噪声模型学到的 w 和 b 不会和真实值完全一样但非常接近。这正是回归模型的工作方式从带噪声的数据中尽可能恢复出背后的真实规律。4.2 用 sklearn 实现同样效果如果你在实际项目中使用通常不会手动实现最小二乘法而是直接调用第三方库。scikit-learn 是 Python 最常用的机器学习库之一它的LinearRegression底层默认使用的就是最小二乘法。from sklearn.linear_model import LinearRegression # sklearn 要求特征是一个二维数组 X_2d X.reshape(-1, 1) # 创建并训练模型 model LinearRegression() model.fit(X_2d, y) # 查看模型参数 w_sk model.coef_[0] b_sk model.intercept_ print(fsklearn 求解结果: w {w_sk:.4f}, b {b_sk:.4f}) # 预测 y_pred model.predict(X_2d)这里有两个新手容易困惑的地方为什么需要X.reshape(-1, 1)因为 sklearn 的模型接口设计为支持多特征输入即使只有一个特征也要显式指定成“100 行 1 列”的形状而不是“100 个元素”的一维数组。coef_和intercept_为什么带下划线这是 sklearn 的约定通过训练计算得到的属性都带下划线后缀用来区分用户手动设置的参数。4.3 可视化拟合结果只输出数字不够直观我们把数据和拟合直线画出来。plt.figure(figsize(8, 5)) plt.scatter(X, y, alpha0.6, label真实数据) plt.plot(X, y_pred, colorred, linewidth2, label拟合直线) plt.xlabel(特征 X) plt.ylabel(目标值 y) plt.title(简单线性回归拟合效果) plt.legend() plt.grid(True, linestyle--, alpha0.6) plt.show()你会看到一条穿过数据点带状的红色直线。由于噪声的存在数据点不会全部落在直线上但直线基本处于点的“正中间”这就是最小二乘法的直观效果让所有点到直线的垂直距离平方和最小。4.4 使用梯度下降方式求解为了加深理解这里再给出一个梯度下降的简洁实现。它不使用解析解而是通过迭代慢慢逼近最优参数。def gradient_descent(X, y, lr0.01, epochs1000): 梯度下降法求解简单线性回归 n len(X) w 0.0 b 0.0 for epoch in range(epochs): y_pred w * X b # 计算梯度 dw (2 / n) * np.sum((y_pred - y) * X) db (2 / n) * np.sum(y_pred - y) # 更新参数 w w - lr * dw b b - lr * db # 每 200 轮打印一次损失 if epoch % 200 0: loss np.mean((y_pred - y) ** 2) print(fEpoch {epoch}: w {w:.4f}, b {b:.4f}, loss {loss:.4f}) return w, b w_gd, b_gd gradient_descent(X, y, lr0.01, epochs1000) print(f梯度下降求解结果: w {w_gd:.4f}, b {b_gd:.4f})运行后你会看到参数随着迭代次数的增加越来越接近最优值。这个例子用的是批量梯度下降每一轮都要把所有样本的梯度加起来再更新。如果数据集特别大更常用的是随机梯度下降或小批量梯度下降它们的区别在于每轮使用的样本数量。4.5 回归模型的评价指标模型训练好了如何量化“预测得好不好”下面三个指标是必须掌握的MSE均方误差所有残差平方的平均值。RMSE均方根误差对 MSE 开根号使量纲和原始 y 一致更便于业务解释。R²决定系数表示模型解释了目标变量多少比例的方差最大为 1越接近 1 拟合越好。from sklearn.metrics import mean_squared_error, r2_score mse mean_squared_error(y, y_pred) rmse np.sqrt(mse) r2 r2_score(y, y_pred) print(fMSE {mse:.4f}) print(fRMSE {rmse:.4f}) print(fR² {r2:.4f})R² 的计算逻辑是如果我们不用任何特征直接拿 y 的均值作为预测值会有一个“基准误差”使用模型后误差减少的比例就是 R²。公式如下R² 1 - (残差平方和 / y 的总平方和)5. 常见问题与排查思路实际写代码跑模型时你可能会遇到各种各样的问题。我把最高频的情况整理成了下面这个表格。问题现象常见原因解决思路模型参数不收敛loss 反而越来越大学习率设置过大参数在最优值附近震荡甚至发散调小学习率例如从 0.01 降到 0.001训练速度非常慢学习率过小或者数据没有做标准化处理适当增大学习率对特征做标准化预测结果全部接近同一个值特征与目标之间没有线性关系或者模型太简单画散点图检查数据分布尝试多项式回归或非线性模型训练集效果很好测试集效果很差过拟合模型过于复杂或训练数据太少增加数据量、使用正则化、简化模型拟合直线明显受个别点影响数据中存在极端异常值最小二乘法对异常值敏感先做异常值检测考虑使用稳健回归不同特征数值量级差距很大其中一个特征尺度过大主导了梯度使用标准化或归一化处理让所有特征尺度统一下面挑两个重点问题展开说明。5.1 学习率设置不当学习率是梯度下降中最难调的超参数之一。学习率过大时参数更新步伐太大可能越过最优点损失不降反升。学习率过小时虽然最终能收敛但需要非常多的迭代次数。建议的做法是先用一个较小的学习率跑 100 轮观察 loss 的下降幅度如果不下降就继续调小如果下降太慢再适当调大。也可以使用学习率衰减策略让训练后期步长自动变小。5.2 异常值对回归的影响最小二乘法在计算时把每个点的残差都平方了异常值因为残差很大平方后会占据非常大的权重。这意味着一个异常点就足以把拟合直线拉向自己。解决办法包括在建模前先画箱线图或散点图检查是否有异常值。使用 Huber 回归或 RANSAC 等对异常值更稳健的算法。如果异常值来自数据记录错误直接清洗掉。6. 最佳实践与工程建议6.1 建模前先画图永远不要跳过这一步很多刚入门的同学拿到数据后第一件事就是model.fit()这是非常危险的。你应该先做探索性数据分析EDA至少画出特征和目标变量的散点图确认两者之间是否存在线性关系。如果散点图显示的是明显的曲线关系用简单线性回归硬拟合只会得到很差的结果这时候应该考虑多项式回归或其他非线性模型。6.2 正确划分训练集和测试集在评估模型时永远不要用训练模型的数据来评价模型好坏。正确的做法是把数据切分成训练集和测试集from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X_2d, y, test_size0.2, random_state42 ) model LinearRegression() model.fit(X_train, y_train) y_test_pred model.predict(X_test) test_mse mean_squared_error(y_test, y_test_pred) print(f测试集 MSE {test_mse:.4f})random_state参数的作用是固定随机切分的结果保证每次运行代码得到同样的划分这一点在复现实验结果时尤其重要。6.3 检查模型假设是否满足线性回归虽然简单但它有一些前提假设。如果这些假设被严重违背模型的可靠性会大打折扣线性关系特征和目标之间必须是线性关系。误差独立样本之间不能相互影响时间序列数据中很容易违反。同方差性残差的方差应该大致稳定不能随着预测值变大而变大。误差正态分布虽然对参数估计影响不大但在做置信区间和假设检验时比较重要。实际工作中不需要每条都严格满足但当你发现模型表现异常时可以从这些假设入手排查。6.4 用简单模型做基线在企业项目中我的经验是先跑通一个简单线性回归作为基线模型记录它的评估指标。然后再尝试更复杂的模型只有复杂模型在测试集上的表现明显优于基线时才值得替换。这样做的原因有两点简单模型可解释性强业务方更容易信任。复杂模型的提升如果很小可能只是噪声带来的偶然结果不值得增加维护成本。6.5 注意特征与目标的数据泄漏数据泄漏是机器学习项目里非常隐蔽又严重的问题。它指的是在训练阶段模型“偷看”了本不应该看到的信息导致测试指标虚高上线后效果大幅缩水。在线性回归中常见的数据泄漏包括在划分训练集之前就对全量数据做标准化或归一化。使用包含了目标变量信息的特征做预测。正确的标准化方式应该是先切分数据再在训练集上计算均值和标准差然后用同样的参数去转换测试集。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 注意这里不能再 fit注意第二行和第三行的区别测试集只能用训练集拟合好的 scaler 做转换不能重新计算均值和标准差。7. 学习路线与下一步方向到这里你已经掌握了简单线性回归的核心内容从直觉理解到数学原理从手写代码到调用 sklearn从模型训练到效果评估。下一步建议按这个顺序继续学习多元线性回归把特征从 1 个扩展到多个理解矩阵运算在机器学习中的核心作用。多项式回归解决数据呈曲线分布的问题理解如何通过特征工程扩展线性模型的能力。正则化方法学习岭回归、LASSO理解它们如何抑制过拟合和进行特征选择。分类问题线性回归解决的是回归问题接下来可以学习逻辑回归它是连接回归与分类的桥梁。如果希望深入研究数学推荐温习一下矩阵求导和概率论中的极大似然估计。线性回归的最小二乘解本质上就是在误差服从正态分布的假设下极大似然估计的解析解。理解这一层你对机器学习的理解会有一个质的提升。学完理论之后一定要自己动手跑一遍。你可以找一个公开数据集比如房价预测或销售预测用本文的代码框架从数据探索开始完整走一遍建模流程。遇到问题也不要怕回归模型是最容易排查和调试的模型把报错信息、数据分布、参数设置贴到搜索引擎基本都能找到答案。如果这篇文章对你有帮助可以收藏备用也欢迎转发给身边正在入门机器学习的朋友。有任何疑问可以在评论区留言我们一起讨论。