现实中,线性回归的问题往往是多元的。
1.多元回归模型基本描述
多元线性回归模型是处理具有多个特征(\(X\) 向量)的回归问题。
模型是一个函数,将输入 \(X\) 映射到输出 \(Y\)。
目标值 \(y\) 是由多个特征 \(x_i\) 及其对应的权重 \(w_i\) 的线性组合构成。
\[y = w_1 x_1 + w_2 x_2 + \cdots + w_n x_n + w_0\]上面的是标量形式,也可以写成向量形式/矩阵乘法形式,这是一种更规范、更紧凑的表达方式:
\[\hat{y} = \mathbf{W}^{\text{T}} \mathbf{X} + w_0\]其中,\(\mathbf{W} = \begin{pmatrix} w_1 \\ \vdots \\ w_n \end{pmatrix}\) 是权重向量。\(\mathbf{X} = \begin{pmatrix} x_1 \\ \vdots \\ x_n \end{pmatrix}\) 是特征向量。\(w_0\)(或 \(b\))是偏置(截距)项。
模型的目标是找到最优的权重 \(\mathbf{W}\) 和偏置 \(w_0\),使得预测值 \(\hat{y}\) 与真实值 \(y\) 之间的误差最小。
和一元的线性回归问题一样,衡量模型性能最常用的指标还是MSE(均方误差):
\[\text{MSE} = \frac{1}{n}\sum_{i=1}^n (\hat{y}_i - y_i)^2\]为了将偏置项 \(w_0\) 融入矩阵乘法中,使其成为权重 \(\mathbf{W}\) 的一部分,我们需要对特征向量 \(\mathbf{X}\) 进行增广(Augmentation):
增广特征向量 \(\mathbf{X}\):在原始特征向量 \(\mathbf{X}\) 的第一个位置(或最后一个位置)添加一个恒为1的元素。
\[\mathbf{X} = \begin{pmatrix} 1 \\ x_1 \\ x_2 \\ \vdots \\ x_n \end{pmatrix}\]这个常数 $1$ 确保了 $w_0$ 在矩阵乘法中被保留。
增广权重向量 \(\mathbf{W}\): 将偏置项 \(w_0\) 作为权重向量 \(\mathbf{W}\) 的第一个元素(对应于增广特征1)。
\[\mathbf{W} = \begin{pmatrix} w_0 \\ w_1 \\ w_2 \\ \vdots \\ w_n \end{pmatrix}\]通过这种增广,多元线性回归模型可以统一表达为一个简单的向量内积(矩阵乘法):
\[\hat{y} = \mathbf{W}^{\text{T}} \mathbf{X}\]或
\[\hat{y} = \mathbf{X}^{\text{T}} \mathbf{W}\]这种简化表达是机器学习和深度学习中的标准做法,简化所有数学推导(如梯度、正规方程)。同时简化了编程实现,而且MSE 损失函数仍然适用于这种简化形式:
\[\text{MSE} = \frac{1}{m}\sum_{i=1}^m (\mathbf{W}^{\text{T}} \mathbf{X}_i - y_i)^2\]这种简洁的表达方式使得复杂的多元线性回归模型在代数和编程上都变得高效和优雅。
2.多元回归-实战
1.读取数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import numpy as np
import ast
def load_data_from_list_format(file_path):
"""
加载并解析您的特定格式([[...], ...])的数据文件。
"""
features = []
targets = []
with open(file_path, 'r') as f:
for line in f:
# 1. 使用 ast.literal_eval 安全地将字符串解析为 Python 列表
try:
data_tuple = ast.literal_eval(line.strip())
# 2. 解包:第一个元素是特征列表,第二个是目标值
X_row = data_tuple[0]
y_value = data_tuple[1]
features.append(X_row)
targets.append(y_value)
except (ValueError, SyntaxError) as e:
print(f"Skipping line due to error: {e} in line: {line.strip()}")
continue
# 3. 转换为 NumPy 数组
X = np.array(features)
y = np.array(targets)
return X, y
# 示例调用
file_path = "../boston/train_data"
X_train, y_train = load_data_from_list_format(file_path)
print(f"Features (X_train) shape: {X_train.shape}")
print(f"Targets (y_train) shape: {y_train.shape}")
print("\nFirst row of features:")
print(X_train[0])
print("\nFirst target value:")
print(y_train[0])

2.使用多元线性回归模型对加载的全部数据进行训练,并打印出模型的参数(即截距和系数)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# ----------------- 模型训练 -----------------
print("🚀 正在使用多元线性回归模型进行训练...")
# 1. 初始化模型
model = LinearRegression()
# 2. 训练模型
# 注意:在实际应用中,您通常会划分训练集和测试集,但根据您的请求,我们使用全部数据进行训练。
model.fit(X_train, y_train)
print("✅ 模型训练完成。")
print("\n--- 模型参数 ---")
# 3. 打印模型参数
# 截距 (Intercept)
print(f"**截距 (Intercept, $\\beta_0$):** {model.intercept_:.4f}")
# 系数 (Coefficients)
print("\n**特征系数 (Coefficients, $\\beta_{1}$ 到 $\\beta_{13}$):**")
# 将系数打印成易于阅读的格式
coefficients = model.coef_
for i, coef in enumerate(coefficients):
# 假设特征索引对应于第 1 到 第 13 个特征
print(f" - Feature {i+1} (X{i+1}): {coef:.4f}")
# 如果您想以 NumPy 数组形式查看所有系数
# print(f"\n所有系数数组: \n{coefficients}")

注意,上面的13个变量没有进行标准化,只有在所有特征(自变量 \(X_i\))都经过标准化(Standardization)处理,即它们具有相同的尺度(例如,均值为 0,标准差为 1)时,参数(系数)的绝对值越大,才说明该变量对 \(Y\) 的影响越大。
在训练模型之前,对所有特征 \(X\) 进行标准化(StandardScaler),然后再次训练模型。
\[\text{标准化} (X') = \frac{X - \mu}{\sigma}\]
3.多项式回归
使用线性回归的前提条件:数据尽量在一条直线上
但是如果有下面这样的数据,如何拟合?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import numpy as np
def load_and_clean_data(file_path):
"""
从指定文件中读取数据,清理字符串格式,并返回 NumPy 数组。
数据格式预期为:[[x], [y]]
"""
cleaned_data_list = []
line_number = 0 # 用于跟踪行号
try:
with open(file_path, 'r') as f:
for line in f:
line_number += 1
# 1. 清理行首和行尾的空白字符
line_stripped = line.strip()
# 严格跳过空行或仅包含空白字符的行
if not line_stripped:
# 如果需要调试,可以取消注释下面这行
# print(f"跳过文件中的空行或空白行 (行号: {line_number})")
continue
# 2. 移除所有方括号 '[]' 和空格,只留下数字和逗号
# 示例: "[[5.34...], [30.91...]]" -> "5.34...,30.91..."
clean_value_str = line_stripped.replace('[', '').replace(']', '').replace(' ', '')
# 3. 按逗号分隔,得到 X 和 y 的字符串
x_str, y_str = clean_value_str.split(',')
# 4. 转换为浮点数并存储
x_val = float(x_str)
y_val = float(y_str)
cleaned_data_list.append([x_val, y_val])
except FileNotFoundError:
print(f"❌ 错误:未找到文件 {file_path}。请检查文件路径。")
return None, None
except ValueError as e:
print(f"❌ 错误:数据格式不正确,无法转换为浮点数。请检查数据行 (行号: {line_number}): {line_stripped}")
print(f"具体错误信息: {e}")
return None, None
except Exception as e:
print(f"❌ 发生未知错误: {e}")
return None, None
# 5. 转换为最终的 NumPy 数组
cleaned_data_np = np.array(cleaned_data_list, dtype=np.float64)
# 6. 分离特征 (X) 和目标值 (y)
# X 必须是二维数组 (n, 1)
X = cleaned_data_np[:, 0].reshape(-1, 1)
y = cleaned_data_np[:, 1] # y 是一维数组 (n,)
return X, y
# ----------------- 修改后的使用示例 -----------------
file_name = 'train_paracurve_data' # 建议使用 .txt 扩展名,以明确文件类型
# **注意:** 请确保您的数据已保存到指定的文件中
X, y = load_and_clean_data(file_name)
if X is not None and y is not None:
print("--- ✅ 数据读取成功 ---")
print(f"特征 X 的形状: {X.shape}")
print(f"目标 y 的形状: {y.shape}")
print("X 的前 5 个样本:\n", X[:5])
print("y 的前 5 个样本:\n", y[:5])
print("\n数据已清理并准备好进行模型训练(未分割数据集)。")
# **********************************************
# 移除的分割代码原本在此处,现在直接对 X 和 y 进行操作
# **********************************************
# 现在您可以直接使用整个 X 和 y 进行模型训练(例如,训练集就是整个数据集)
# from sklearn.linear_model import LinearRegression
# model = LinearRegression()
# model.fit(X, y) # 直接使用所有数据进行训练
if X is not None and y is not None:
print("🎨 正在生成散点图...")
# 绘制散点图
plt.figure(figsize=(10, 6))
# 绘制原始数据点 (散点图)
# X 必须被展平为一维数组才能作为 x 轴输入
plt.scatter(X.flatten(), y, color='blue', alpha=0.7)
# 添加标题和标签
plt.title('特征 X 与目标 Y 的数据散点图', fontsize=16)
plt.xlabel('特征 X 值', fontsize=14)
plt.ylabel('目标值 Y 值', fontsize=14)
# 显示网格
plt.grid(True, linestyle='--', alpha=0.6)
# 显示图形
plt.show()
print("✅ 散点图绘制完成。")

可以用下面的模型拟合:(因为通过观察猜测可以用抛物线拟合)
\[y = a x_1^2 + b x_1 + c\]这被称为 二次多项式回归 (Quadratic Polynomial Regression)。
使用 sklearn 的 PolynomialFeatures 来生成 \(x^2\) 和 \(x\) 这两个特征,然后使用 LinearRegression 进行拟合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score
# 为保证绘图平滑,我们对 X 进行排序
sort_index = np.argsort(X.flatten())
X_sorted = X[sort_index]
y_sorted = y[sort_index]
# -------------------------- 1. 特征工程:生成多项式特征 --------------------------
# 我们需要创建 X^2 和 X 这两个特征
# 公式: y = a*x^2 + b*x + c
# 这相当于一个线性模型: y = a*Z1 + b*Z2 + c,其中 Z1=x^2, Z2=x
# degree=2 会生成 [1, x, x^2] (如果 include_bias=True)
# 我们设置为 include_bias=False,因为 LinearRegression 默认会添加截距 (c)
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X_sorted)
# X_poly 现在有两列: [x, x^2] (注意:PolynomialFeatures 默认按幂次升序 [x^1, x^2])
# 为了对应您公式中的顺序 (x^2, x),我们手动调整列的顺序(可选,但有助于参数对应)
# 确保 X_poly 的列顺序是 [x^2, x]
# 实际上,由于系数是自动拟合的,保持默认顺序 [x, x^2] 更简单。
# 我们保持默认顺序,并在解释系数时说明。
# 默认列顺序: X_poly = [x^1, x^2]
# -------------------------- 2. 模型训练 --------------------------
model = LinearRegression()
model.fit(X_poly, y_sorted)
# 3. 预测拟合曲线
# 用于绘图的预测值
y_pred = model.predict(X_poly)
# -------------------------- 4. 打印参数 --------------------------
# 系数顺序: model.coef_[0] 对应 x, model.coef_[1] 对应 x^2
# 截距: model.intercept_ 对应 c
coef_x = model.coef_[0]
coef_x2 = model.coef_[1]
intercept_c = model.intercept_
print("--- ✅ 模型训练完成 ---")
print(f"拟合公式: y = {coef_x2:.4f} * x^2 + {coef_x:.4f} * x + {intercept_c:.4f}")
print("--- 模型参数 ---")
print(f"a (x^2 的系数): {coef_x2:.4f}")
print(f"b (x 的系数): {coef_x:.4f}")
print(f"c (截距): {intercept_c:.4f}")
print(f"拟合优度 R²: {r2_score(y_sorted, y_pred):.4f}")
# -------------------------- 5. 绘图 --------------------------
# Matplotlib 绘图设置
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示的问题
plt.figure(figsize=(10, 6))
# 绘制原始数据点 (散点图)
plt.scatter(X_sorted.flatten(), y_sorted, color='blue', alpha=0.7, label='原始数据点')
# 绘制拟合曲线
plt.plot(X_sorted.flatten(), y_pred, color='red', linewidth=3, label=f'拟合曲线: y = a*x² + b*x + c')
# 添加标题和标签
plt.title('二次多项式回归拟合', fontsize=16)
plt.xlabel('特征 X', fontsize=14)
plt.ylabel('目标值 Y', fontsize=14)
# 添加图例
plt.legend()
# 显示网格
plt.grid(True, linestyle='--', alpha=0.6)
# 显示图形
plt.show()

\(R^2\) (R-squared) ,也称为决定系数 (Coefficient of Determination),是用来衡量建立的回归模型对原始数据的拟合程度的一个指标。计算公式如下(非必要了解,但可帮助理解):
\[R^2 = 1 - \frac{SS_{res}}{SS_{tot}}\]- \(SS_{res}\)(Residual Sum of Squares):残差平方和,即预测值与实际值之间差异的平方和。
- \(SS_{tot}\)(Total Sum of Squares):总平方和,即实际值与实际值平均值之间差异的平方和(代表了 $Y$ 的总变异)。
\(R^2\) 的取值范围通常在 0 到 1 之间。
| R^2 值 | 含义 |
|---|---|
| \(R^2 \approx 1\) | 模型能很好地解释数据的变异。这意味着模型对数据的拟合非常好,预测值与实际值非常接近。 |
| \(R^2 \approx 0\) | 模型几乎不能解释数据的变异。这意味着模型的预测能力很弱,可能还不如直接使用 \(Y\) 的平均值来预测。 |
| \(R^2 < 0\) | 这种情况很少见,但可能发生在拟合一个非线性模型(如您刚才做的多项式回归)或使用不合适的模型时。它意味着您的模型拟合效果比简单地使用 \(Y\) 的平均值进行预测还要差。 |
上面演示的是二次,三次甚至高次都是同理。
根据泰勒公式,任意函数都可以表示成\(y = \beta_n x^n + \beta_{n-1} x^{n-1} + \dots + \beta_1 x + \beta_0\)
根据 泰勒公式 (Taylor’s Formula),在一定条件下,一个平滑(可导)的函数 \(f(x)\) 可以在某一点 \(x_0\) 附近用一个多项式来近似表示:
\[f(x) \approx f(x_0) + f'(x_0)(x-x_0) + \frac{f''(x_0)}{2!}(x-x_0)^2 + \dots + \frac{f^{(n)}(x_0)}{n!}(x-x_0)^n\]这正是多项式回归的理论基础!
多项式回归的本质就是利用线性模型来拟合特征的多项式组合,从而利用泰勒多项式的能力去逼近数据集中存在的任何潜在的非线性关系。
只需通过 PolynomialFeatures(degree=n) 来生成 \(x\) 的 \(n\) 次方直到 1 次方的特征,然后将其输入到线性回归模型中进行拟合即可。
次数越高,计算量不一定是越来越大的(因为线性回归的计算复杂度主要取决于样本量 \(N\) 和特征数 \(P\)),但模型引入的风险是越来越高的。理由如下:
1.过拟合是最主要的问题。高次多项式能够完美穿过所有训练数据点,但它学到的是数据中的噪声和随机误差,而不是数据背后的真实规律。这会导致模型在新的、未见过的数据上的泛化能力极差。
2.共线性:当 \(x\) 的数值范围较大时,\(x^2, x^3, \dots, x^n\) 这些高次项之间的相关性会非常高,导致特征共线性。这会使回归系数 \(\beta_i\) 的估计变得非常不稳定,微小的数据变动都可能导致系数的巨大变化。
3.计算量。虽然对单变量而言计算量增加不显著,但对于多变量(如 \(x_1, x_2\))的高次模型,特征数量会呈指数级增长,此时计算时间和内存消耗会急剧增加。
4.随着多项式次数的增加,模型的系数变得难以解释,降低了模型的可读性和实用性。
因此,在实践中,我们通常选择一个较低的次数(如 2 次或 3 次),并在拟合度(\(R^2\))和模型复杂度之间找到最佳的平衡点。
另外,如果模型过拟合(在训练集上学习过多噪声),不一定在测试集上就一定好,所以次数一定不是越高越好。(虽然次数越高,模型对于训练集的拟合就越好)。这也是一种trade off
4.花式玩法-抗噪声
考虑这样的问题,原来有数据\([X, y]\),现在引入随机产生的数据\(X_2\),就有了\([X_1, X_2, y]\),请问最后多元线性回归拟合的\(\hat{y}=w_1X_1+w_2X_2+b\)中,\(w_2\)的取值是不是0?
结论:\(w_2\) 的期望值是0,但实际值可能不为0
\(X_2\) 是完全随机生成的(例如,来自 \(\mathcal{N}(0, 1)\) 或 \(\mathcal{U}(a, b)\) 分布)。\(X_2\) 与原始特征 \(X_1\) 完全独立,并且与目标变量 \(y\) 完全独立(即它们之间没有真实的线性关系)。
在线性回归中,模型的系数 \(w_i\) 旨在最小化残差平方和(最小二乘法)。如果 \(X_2\) 与 \(y\) 之间没有真实关系,那么在无限大的样本集上,最小化残差平方和的最佳解是设置 \(w_2\) 为 $0$,因为任何非零的 \(w_2\) 只会引入额外的、不可解释的误差。
\[E[w_2] = 0\]实际原因 (有限样本集和随机误差):
在任何有限的实际数据集上,即使 \(X_2\) 是纯随机的,它也会由于抽样误差(Sampling Error)和随机噪声而出现以下情况:
- 偶然相关性 (Spurious Correlation): 随机生成的 \(X_2\) 与目标变量 \(y\) 之间会存在微弱、偶然的非零相关性。最小二乘法会试图利用这种微弱的、随机的相关性来进一步稍微降低残差平方和。
- 噪声吸收: \(X_2\) 可能会“吸收”模型中未被 \(X_1\) 解释的随机噪声部分。模型可能会将 \(w_2\) 设置为一个微小的非零值,以利用 \(X_2\) 的随机波动来微调拟合,使 \(\sum (y - \hat{y})^2\) 略微减小。
在实际运行代码时,您会发现拟合得到的 \(w_2\) 是一个接近于 0 的非零小数
下面是模拟的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# --- 1. 创建模拟数据 ---
np.random.seed(42) # 设置随机种子以保证结果可重现
N_SAMPLES = 1000 # 样本数量
TRUE_W1 = 5.0 # X1 的真实系数
TRUE_B = 10.0 # 真实截距
# 原始特征 X1 (与 y 有关)
X1 = np.random.rand(N_SAMPLES, 1) * 10
# 目标变量 y (包含随机噪声)
# y = 5*X1 + 10 + 噪声
y = TRUE_W1 * X1.flatten() + TRUE_B + np.random.normal(0, 2, N_SAMPLES)
# --- 2. 引入随机特征 X2 ---
# X2 是一个完全随机的特征,与 X1 和 y 均独立
X2 = np.random.normal(0, 5, (N_SAMPLES, 1))
# --- 3. 组合特征矩阵 ---
# 现在的特征矩阵 X 包含 X1 和 X2
# X.shape -> (1000, 2)
X_combined = np.hstack((X1, X2))
print(f"原始特征 X1 形状: {X1.shape}")
print(f"随机特征 X2 形状: {X2.shape}")
print(f"组合特征 X 形状: {X_combined.shape}")
# --- 4. 训练多元线性回归模型 ---
model = LinearRegression()
model.fit(X_combined, y)
# --- 5. 打印结果 ---
w1_fit = model.coef_[0]
w2_fit = model.coef_[1]
b_fit = model.intercept_
print("\n--- 模型拟合结果 ---")
print(f"特征 X1 的真实系数 (w1): {TRUE_W1:.4f}")
print(f"拟合得到的 X1 系数 (w1): {w1_fit:.4f}")
print("-" * 30)
print(f"随机特征 X2 的真实系数 (w2): 0.0000")
print(f"拟合得到的 X2 系数 (w2): {w2_fit:.8f}")
print("-" * 30)
print(f"截距的真实值 (b): {TRUE_B:.4f}")
print(f"拟合得到的截距 (b): {b_fit:.4f}")

上面的例子可以说明线性回归具有抗噪声的能力。
5.花式玩法-共线性
假设我们从原始特征 \(x_1\) 构造了两个完全相同的特征 \(X_{1a} = x_1\)和 \(X_{1b} = x_1\),并将它们代入模型:
\[y = w_{1a} X_{1a} + w_{1b} X_{1b} + w_0\]这种复制特征的操作创建了完美的共线性(Perfect Collinearity),即特征 \(X_{1a}\) 和 \(X_{1b}\) 之间的相关系数为1。
线性回归模型(最小二乘法)的优化目标是找到最优的权重组合 \((w_{1a}, w_{1b})\),使得残差平方和最小。
假设原始的真实关系是 \(y = W_{\text{true}} x_1 + w_0\)。
当引入 \(X_{1a}\) 和 \(X_{1b}\) 时,模型要拟合的是:
\[y = w_{1a} x_1 + w_{1b} x_1 + w_0 = (w_{1a} + w_{1b}) x_1 + w_0\]问题在于: 只要保持系数之和 \((w_{1a} + w_{1b})\) 等于原始的真实系数 \(W_{\text{true}}\),模型的预测结果 \(\hat{y}\) 就不会改变,残差平方和(RSS)也保持不变。
因此,模型无法确定 \(w_{1a}\) 和 \(w_{1b}\) 的具体取值,只要满足:
\[w_{1a} + w_{1b} = W_{\text{true}}\]即,存在无穷多组解,它们都能最小化 RSS。这使得系数矩阵在数学上是奇异的(不可逆),导致最小二乘解变得不确定或不稳定。
这种现象说明了线性回归的一个重要特性:
线性回归对特征间的共线性高度敏感。当特征之间存在高度相关性时,模型的系数会被“摊派”,变得不稳定、难以解释,并且在不同数据集上运行时,系数的取值可能会发生剧烈变化。
因此,在实际应用中,处理共线性是特征工程的一个重要环节,常用的解决方法包括:
- 特征选择: 删除重复或高度相关的特征(例如,在这个例子中删除 \(X_{1b}\))。
- 正则化: 使用 岭回归 (Ridge Regression) 或 Lasso 回归,它们通过引入惩罚项来稳定和约束系数,减轻共线性的影响。
加噪声对于线性回归没有伤害,加相关性强的变量也不会带来帮助。这是线性回归很重要的特点。