行莫
行莫
发布于 2025-11-27 / 32 阅读
0
0

一元线性回归正规方程解

一元线性回归正规方程解

线性回归(Linear Regression) 是通过找到输入和输出之间的线性关系(通过拟合一条直线或超平面),来预测未知的结果。

一元线性回归是最简单的线性回归形式,只有一个输入特征和一个输出。

比如房屋面积和房屋价格之间的线性关系,输入房屋面积输出房屋价格,来进行价格预测。

样本数据

面积(平方米)价格(万元)
80120
95140
120180
130200
75110
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
$$

带入样本数据:

面积(平方米)价格(万元)
80120
95140
120180
130200
75110

$$
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 是最常用的损失函数,因为:

  1. 数学性质好:可导、凸函数,保证有全局最优解
  2. 有解析解:可以使用正规方程法直接求解
  3. 适合梯度下降:梯度计算简单
  4. 理论成熟:最小二乘法理论完善

因此,我们之前使用的损失函数 $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$),计算会变得非常慢
  • 不适合非凸优化问题,正规方程法只适用于线性回归等凸优化问题,对于神经网络等非凸问题,无法使用


评论