一元线性回归正规方程解

线性回归(Linear Regression) 是通过找到输入和输出之间的线性关系(通过拟合一条直线或超平面),来预测未知的结果。
一元线性回归是最简单的线性回归形式,只有一个输入特征和一个输出。
比如房屋面积和房屋价格之间的线性关系,输入房屋面积输出房屋价格,来进行价格预测。
样本数据
| 面积(平方米) | 价格(万元) |
|---|---|
| 80 | 120 |
| 95 | 140 |
| 120 | 180 |
| 130 | 200 |
| 75 | 110 |
| 110 | ? |

一元线性回归公式
一元线性回归的公式为:
$y = kx + b$
其中:
- $y$ 是预测值,即房屋价格
- $x$ 是特征,即房屋面积
- $k$ 是权重,也叫斜率,表示房屋面积对价格之间的关系
- $b$ 是偏置,也叫截距,表示价格在截距处的值
找到最佳拟合线就是求斜率和截距的过程了

误差
误差 = 预测值 - 真实值
误差的绝对值越小,误差越小,预测值越接近真实值
将输入 x 带入 $y = kx + b$ 得到的是 预测试

损失函数
单个样本的误差 = 预测值 - 真实值 = (kx + b) - 真实值
所有样本的误差和 = 单个样本的误差绝对值累加和
误差平均值 = 所有样本的误差和 / 样本个数
损失函数 L(k,b) 等于每个样本的误差和(越小越好)
这样就得到了损失函数:
$$
L(k,b) = \sum_{i=1}^{n} (kx_i + b - y_i)^2
$$
该损失函数是误差平方和 SSE,通常使用的损失函数是均方误差 MSE ($SSE / n$):
$$
L(k,b) = \frac{1}{n} \sum_{i=1}^{n} (kx_i + b - y_i)^2
$$
该损失函数是一个三维曲面,k 是 x 轴 ,b 是 y 轴 ,L(k,b) 是 z 轴。

现在问题就变成找到该损失函数的最小值,最小值点意味着误差最小。
将损失函数 L(k , b) 与 k 映射到二维平面坐标系, k 映射到 x 轴,L(k,b) 映射到 y 轴。
将损失函数 L(k , b) 与 b 映射到二维平面坐标系, b 映射到 x 轴,L(k,b) 映射到 y 轴。
该损失函数是凸函数,有唯一的最小值点,导数为0的点就是极小值点。

偏导
导数为 0 的点就是该损失函数的极小值点。
所以现在就要对 k , b 分别求偏导。
偏导就是固定其他量不变,只对一个变化量求导,比如固定 k 不变对 b 求导,看 b 的变化会如何影响 L(k,b)。
损失函数:
$$
L(k,b) = \sum_{i=1}^{n} (kx_i + b - y_i)^2
$$
误差 = 预测值 - 真实值可能为正或负。使用平方误差可以消除正负号影响,并对大误差给予更大惩罚。
对 k 求偏导(把 b 视作常数)
$$
\frac{\partial L}{\partial k}
= 2 \sum_{i=1}^{n} (kx_i + b - y_i)(kx_i + b - y_i)
$$
$$
\frac{\partial L}{\partial k}
= 2 \sum_{i=1}^{n} (kx_i + b - y_i)x_i
$$
$$
\frac{\partial L}{\partial k}
= 2 \sum_{i=1}^{n} (kx_i^{2} + bx_i - y_ix_i)
$$
导数为 0 的点就是该损失函数的极小值点,所以:
$$
0 = 2 \sum_{i=1}^{n} (kx_i^{2} + bx_i - y_ix_i)
$$
消去 2:
$$
0 = \sum_{i=1}^{n} (kx_i^{2} + bx_i - y_ix_i)
$$
展开:
$$
0 = \sum_{i=1}^{n} kx_i^{2} + \sum_{i=1}^{n} bx_i - \sum_{i=1}^{n} y_ix_i
$$
变形方便计算:
$$
0 = k\sum_{i=1}^{n} x_i^{2} + b\sum_{i=1}^{n} x_i - \sum_{i=1}^{n} y_ix_i
$$
带入样本数据:
| 面积(平方米) | 价格(万元) |
|---|---|
| 80 | 120 |
| 95 | 140 |
| 120 | 180 |
| 130 | 200 |
| 75 | 110 |
$$
0 = k(80^2 + 95^2 + 120^2 + 130^2 + 75^2) + b(80 + 95 + 120 + 130 + 75) - (80 \times 120 + 95 \times 140 + 120 \times 180 + 130 \times 200 + 75 \times 110)
$$
$$
0 = 52350k + 500b - 78750
$$
对 b 求偏导(把 k 视作常数)
$$
\frac{\partial L}{\partial b}
= 2 \sum_{i=1}^{n} (kx_i + b - y_i)(kx_i + b - y_i)
$$
$$
\frac{\partial L}{\partial b}
= 2 \sum_{i=1}^{n} (kx_i + b - y_i)1
$$
$$
\frac{\partial L}{\partial b}
= 2 \sum_{i=1}^{n} (kx_i + b - y_i)
$$
导数为 0 的点就是该损失函数的极小值点,所以:
$$
0 = 2 \sum_{i=1}^{n} (kx_i + b - y_i)
$$
展开:
$$
0 = \sum_{i=1}^{n} kx_i + \sum_{i=1}^{n} b - \sum_{i=1}^{n} y_i
$$
变形方便计算:
$$
0 = k\sum_{i=1}^{n} x_i + nb - \sum_{i=1}^{n} y_i
$$
带入样本数据:
$$
0 = k(80 + 95 + 120 + 130 + 75) + 5b - (120 + 140 + 180 + 200 + 110)
$$
$$
0 = 500k + 5b - 750
$$
所以:
$$
52350k + 500b + 77800 = 500k + 5b - 750
$$
得出:
$$k = 1.5957 , b = -9.5745$$
验证求解出的 k、b 值是否为最佳拟合线
import numpy as np
import matplotlib.pyplot as plt
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 样本数据
areas = np.array([80, 95, 120, 130, 75]) # 面积(平方米)
prices = np.array([120, 140, 180, 200, 110]) # 价格(万元)
n = len(areas)
# 求解出的参数值
k_solved = 1.5957
b_solved = -9.5745
print("=" * 70)
print("验证方法1:检查偏导数是否接近0(最优解的必要条件)")
print("=" * 70)
# 计算偏导数
def partial_derivative_k(k, b, x_data, y_data):
"""对 k 的偏导数"""
return -2 * np.sum(x_data * (y_data - k * x_data - b))
def partial_derivative_b(k, b, x_data, y_data):
"""对 b 的偏导数"""
return -2 * np.sum(y_data - k * x_data - b)
# 计算在求解点处的偏导数
partial_k = partial_derivative_k(k_solved, b_solved, areas, prices)
partial_b = partial_derivative_b(k_solved, b_solved, areas, prices)
print(f"∂L/∂k 在 (k={k_solved}, b={b_solved}) 处的值: {partial_k:.6f}")
print(f"∂L/∂b 在 (k={k_solved}, b={b_solved}) 处的值: {partial_b:.6f}")
print(f"\n✓ 两个偏导数都接近0,说明这是极值点(最优解)")
print("\n" + "=" * 70)
print("验证方法2:计算损失函数值,并与附近点比较")
print("=" * 70)
# 定义损失函数
def loss_function(k, b, x_data, y_data):
"""均方误差损失函数"""
y_pred = k * x_data + b
return np.mean((y_data - y_pred) ** 2)
# 计算求解点的损失值
loss_solved = loss_function(k_solved, b_solved, areas, prices)
# 测试附近的一些点
test_points = [
(k_solved - 0.1, b_solved, "k减小0.1"),
(k_solved + 0.1, b_solved, "k增大0.1"),
(k_solved, b_solved - 5, "b减小5"),
(k_solved, b_solved + 5, "b增大5"),
(k_solved - 0.05, b_solved - 2, "k和b都变化"),
(k_solved + 0.05, b_solved + 2, "k和b都变化"),
]
print(f"\n求解点的损失值: L({k_solved:.4f}, {b_solved:.4f}) = {loss_solved:.6f}\n")
print(f"{'测试点':<20} {'损失值':<15} {'与最优值比较':<20}")
print("-" * 60)
for k_test, b_test, desc in test_points:
loss_test = loss_function(k_test, b_test, areas, prices)
diff = loss_test - loss_solved
comparison = f"大 {diff:.6f}" if diff > 0 else f"小 {abs(diff):.6f}"
print(f"{desc:<20} {loss_test:.6f} {comparison}")
print(f"\n✓ 所有测试点的损失值都大于或等于求解点的损失值")
print(f"✓ 说明求解点 ({k_solved:.4f}, {b_solved:.4f}) 确实是最小值点")
print("\n" + "=" * 70)
print("验证方法3:使用正规方程解验证")
print("=" * 70)
# 使用正规方程解计算
sum_x = np.sum(areas)
sum_y = np.sum(prices)
sum_xy = np.sum(areas * prices)
sum_x2 = np.sum(areas ** 2)
k_normal = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x ** 2)
b_normal = (sum_y - k_normal * sum_x) / n
print(f"正规方程解: k = {k_normal:.6f}, b = {b_normal:.6f}")
print(f"偏导求解: k = {k_solved:.6f}, b = {b_solved:.6f}")
print(f"\n差值: Δk = {abs(k_normal - k_solved):.6f}, Δb = {abs(b_normal - b_solved):.6f}")
print(f"✓ 两种方法得到的结果非常接近(差值在可接受范围内)")
print("\n" + "=" * 70)
print("验证方法4:可视化拟合效果")
print("=" * 70)
# 创建图形
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
# 左图:拟合线和数据点
x_line = np.linspace(areas.min() - 10, areas.max() + 10, 100)
y_line = k_solved * x_line + b_solved
ax1.scatter(areas, prices, color='blue', s=150, alpha=0.8,
label='样本数据点', zorder=5, edgecolors='black', linewidth=1.5)
ax1.plot(x_line, y_line, 'r-', linewidth=3,
label=f'拟合线: y = {k_solved:.4f}x + {b_solved:.4f}', zorder=3)
# 添加误差线
for i in range(len(areas)):
y_pred = k_solved * areas[i] + b_solved
ax1.plot([areas[i], areas[i]], [prices[i], y_pred],
'g--', linewidth=1.5, alpha=0.5, zorder=2)
ax1.set_xlabel('房屋面积 (平方米)', fontsize=12, fontweight='bold')
ax1.set_ylabel('房屋价格 (万元)', fontsize=12, fontweight='bold')
ax1.set_title('拟合效果可视化', fontsize=14, fontweight='bold')
ax1.legend(fontsize=11)
ax1.grid(True, alpha=0.3, linestyle='--')
# 右图:损失函数等高线图(局部)
k_range = np.linspace(k_solved - 0.3, k_solved + 0.3, 50)
b_range = np.linspace(b_solved - 10, b_solved + 10, 50)
K, B = np.meshgrid(k_range, b_range)
Loss = np.zeros_like(K)
for i in range(len(k_range)):
for j in range(len(b_range)):
Loss[j, i] = loss_function(K[j, i], B[j, i], areas, prices)
contour = ax2.contour(K, B, Loss, levels=15, cmap='viridis', alpha=0.8)
ax2.clabel(contour, inline=True, fontsize=8, fmt='%.2f')
contourf = ax2.contourf(K, B, Loss, levels=15, cmap='viridis', alpha=0.6)
# 标记求解点
ax2.plot(k_solved, b_solved, 'r*', markersize=20,
label=f'求解点: k={k_solved:.4f}, b={b_solved:.4f}', zorder=10)
ax2.axvline(x=k_solved, color='red', linestyle='--', linewidth=1.5, alpha=0.5)
ax2.axhline(y=b_solved, color='red', linestyle='--', linewidth=1.5, alpha=0.5)
ax2.set_xlabel('斜率 k', fontsize=12, fontweight='bold')
ax2.set_ylabel('截距 b', fontsize=12, fontweight='bold')
ax2.set_title('损失函数等高线图(局部放大)', fontsize=14, fontweight='bold')
ax2.legend(fontsize=10, loc='upper right')
ax2.grid(True, alpha=0.3, linestyle='--')
plt.colorbar(contourf, ax=ax2, label='损失值')
plt.suptitle('验证:求解出的 k、b 值是否为最佳拟合线',
fontsize=16, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
print("\n" + "=" * 70)
print("总结")
print("=" * 70)
print("✓ 验证方法1:偏导数接近0 → 满足极值条件")
print("✓ 验证方法2:损失值最小 → 确实是最优解")
print("✓ 验证方法3:与正规方程解一致 → 结果正确")
print("✓ 验证方法4:可视化显示拟合效果良好 → 直观验证")
print("\n结论:求解出的 k = 1.5957, b = -9.5745 确实是最佳拟合线的参数!")
print("=" * 70)
======================================================================
验证方法1:检查偏导数是否接近0(最优解的必要条件)
∂L/∂k 在 (k=1.5957, b=-9.5745) 处的值: -4.710000
∂L/∂b 在 (k=1.5957, b=-9.5745) 处的值: -0.045000
✓ 两个偏导数都接近0,说明这是极值点(最优解)
======================================================================
验证方法2:计算损失函数值,并与附近点比较
求解点的损失值: L(1.5957, -9.5745) = 3.191511
测试点 损失值 与最优值比较
k减小0.1 107.985711 大 104.794200
k增大0.1 107.797311 大 104.605800
b减小5 28.236511 大 25.045000
b增大5 28.146511 大 24.955000
k和b都变化 53.431611 大 50.240100
k和b都变化 53.301411 大 50.109900
✓ 所有测试点的损失值都大于或等于求解点的损失值
✓ 说明求解点 (1.5957, -9.5745) 确实是最小值点
======================================================================
验证方法3:使用正规方程解验证
正规方程解: k = 1.595745, b = -9.574468
偏导求解: k = 1.595700, b = -9.574500
差值: Δk = 0.000045, Δb = 0.000032
✓ 两种方法得到的结果非常接近(差值在可接受范围内)

损失函数的分类
针对一元线性回归,常用的损失函数有以下几种:
1. 均方误差(Mean Squared Error, MSE)
定义:所有样本的误差平方的平均值
公式:
$$MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}i)^2 = \frac{1}{n} \sum{i=1}^{n} (y_i - (kx_i + b))^2$$
特点:
- ✅ 最常用的损失函数,数学性质好(可导、凸函数)
- ✅ 对大误差惩罚更重(平方项)
- ✅ 有解析解,适合正规方程法
- ⚠️ 对异常值敏感(平方会放大异常值的影响)
- ⚠️ 单位是原单位的平方(如:万元的平方)
适用场景:大多数线性回归问题,特别是需要精确拟合的情况
2. 均方根误差(Root Mean Squared Error, RMSE)
定义:MSE的平方根
公式:
$$ RMSE = \sqrt{MSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2} $$
特点:
- ✅ 单位与原数据相同(如:万元),更易解释
- ✅ 对大误差的惩罚与MSE相同
- ⚠️ 对异常值同样敏感
- ⚠️ 不是凸函数(开平方),但优化MSE等价于优化RMSE
适用场景:需要与原始数据单位一致的评估指标
3. 平均绝对误差(Mean Absolute Error, MAE)
定义:所有样本的误差绝对值的平均值
公式:
$$ MAE = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}i| = \frac{1}{n} \sum{i=1}^{n} |y_i - (kx_i + b)| $$
特点:
- ✅ 对异常值不敏感(绝对值不会放大异常值)
- ✅ 单位与原数据相同,易于理解
- ✅ 鲁棒性强
- ⚠️ 在零点不可导,不适合梯度下降法
- ⚠️ 没有解析解,需要迭代优化
适用场景:数据中存在异常值,需要鲁棒性强的模型
4. 残差平方和(Sum of Squared Errors, SSE)
定义:所有样本的误差平方的总和(不除以n)
公式:
$$ SSE = \sum_{i=1}^{n} (y_i - \hat{y}i)^2 = \sum{i=1}^{n} (y_i - (kx_i + b))^2 $$
特点:
- ✅ 与MSE等价(SSE = n × MSE)
- ✅ 数学推导中常用
- ⚠️ 受样本数量影响(样本越多,SSE越大)
- ⚠️ 不适合比较不同样本数量的模型
适用场景:数学推导和理论分析
5. 残差绝对值和(Sum of Absolute Errors, SAE)
定义:所有样本的误差绝对值的总和(不除以n)
公式:
$$ SAE = \sum_{i=1}^{n} |y_i - \hat{y}i| = \sum{i=1}^{n} |y_i - (kx_i + b)| $$
特点:
- ✅ 与MAE等价(SAE = n × MAE)
- ✅ 对异常值不敏感
- ⚠️ 受样本数量影响
- ⚠️ 在零点不可导
适用场景:理论分析,需要鲁棒性的场景
6. 平均绝对百分比误差(Mean Absolute Percentage Error, MAPE)
定义:所有样本的百分比误差绝对值的平均值
公式:
$$
MAPE = \frac{100}{n} \sum_{i=1}^{n} \left|\frac{\hat{y}i - y_i}{y_i}\right| = \frac{100}{n} \sum{i=1}^{n} \left|\frac{(kx_i + b) - y_i}{y_i}\right|
$$
特点:
- ✅ 无量纲,便于比较不同量级的数据
- ✅ 以百分比形式表示,易于理解
- ⚠️ 当真实值 $y_i = 0$ 时无法计算
- ⚠️ 对小的真实值惩罚过大
适用场景:需要百分比评估的场景,如预测增长率、比例等
📊 损失函数对比总结
| 损失函数 | 公式 | 单位 | 对异常值 | 可导性 | 解析解 | 常用度 |
|---|---|---|---|---|---|---|
| MSE | $\frac{1}{n}\sum(y_i-\hat{y}_i)^2$ | 原单位² | 敏感 | ✅ 可导 | ✅ 有 | ⭐⭐⭐⭐⭐ |
| RMSE | $\sqrt{MSE}$ | 原单位 | 敏感 | ⚠️ 间接可导 | ✅ 有 | ⭐⭐⭐⭐ |
| MAE | $\frac{1}{n}\sum|y_i-\hat{y}_i|$ | 原单位 | 不敏感 | ❌ 零点不可导 | ❌ 无 | ⭐⭐⭐ |
| SSE | $\sum(y_i-\hat{y}_i)^2$ | 原单位² | 敏感 | ✅ 可导 | ✅ 有 | ⭐⭐⭐ |
| SAE | $\sum|y_i-\hat{y}_i|$ | 原单位 | 不敏感 | ❌ 零点不可导 | ❌ 无 | ⭐⭐ |
| MAPE | $\frac{100}{n}\sum|\frac{y_i-\hat{y}_i}{y_i}|$ | 百分比 | 中等 | ⚠️ 条件可导 | ❌ 无 | ⭐⭐ |
💡 选择建议
选择 MSE 的情况:
- ✅ 数据质量好,异常值少
- ✅ 需要精确拟合
- ✅ 使用正规方程法或梯度下降法
- ✅ 大多数标准线性回归问题
选择 MAE 的情况:
- ✅ 数据中存在异常值
- ✅ 需要鲁棒性强的模型
- ✅ 误差分布不对称
- ✅ 使用迭代优化方法(如梯度下降)
选择 RMSE 的情况:
- ✅ 需要与原始数据单位一致的评估指标
- ✅ 需要直观理解误差大小
- ✅ 大多数情况下与MSE等价
选择 MAPE 的情况:
- ✅ 需要百分比评估
- ✅ 不同量级数据的比较
- ✅ 真实值不为零的数据
🔍 实际应用示例
在一元线性回归中,MSE 是最常用的损失函数,因为:
- 数学性质好:可导、凸函数,保证有全局最优解
- 有解析解:可以使用正规方程法直接求解
- 适合梯度下降:梯度计算简单
- 理论成熟:最小二乘法理论完善
因此,我们之前使用的损失函数 $L(k,b) = \frac{1}{n}\sum_{i=1}^{n}(y_i - (kx_i + b))^2$ 就是 MSE。
正规方程法的优势与弊端
正规方程法(Normal Equation)是通过直接求解偏导数为0的方程组来得到最优参数的方法。
对于一元线性回归 $y = kx + b$,正规方程解为:
$$
k = \frac{n\sum_{i=1}^{n}x_i y_i - \sum_{i=1}^{n}x_i \sum_{i=1}^{n}y_i}{n\sum_{i=1}^{n}x_i^2 - (\sum_{i=1}^{n}x_i)^2}
$$
$$
b = \frac{\sum_{i=1}^{n}y_i - k\sum_{i=1}^{n}x_i}{n}
$$
优势
- 精确解,无需迭代
- 基于严格的数学推导(最小二乘法),对于凸优化问题,保证找到全局最优解
- 不需要特征缩放,正规方程法不需要对特征进行归一化或标准化
- 计算效率高(小规模数据),对于样本数量较少($n < 10^4$)的情况,计算速度很快,时间复杂度为 $O(n)$,其中 $n$ 是样本数量
弊端
- 计算复杂度高(大规模数据),对于大规模数据集($n > 10^4$ 或 $m > 10^4$),计算会变得非常慢
- 不适合非凸优化问题,正规方程法只适用于线性回归等凸优化问题,对于神经网络等非凸问题,无法使用
