写在前面的
这篇文章主要是用来有编程经验的人复习用的笔记,要求如下。
- 知道线性回归是什么(机器学习要过一遍基础。
- 有一定编程经验
※ 如果以上不达标,那么这篇文章肯定就不是写给你的。
1 线性回归
对于有编程经验的人,我个人倾向于通过代码来学习线性回归,代码里不懂的直接去问AI。而且要多问为什么?如果看不懂接下来是什么,那就要随便找一个
线性回归是什么呢
线性回归是一种基础的统计学和机器学习技术,用于通过一个或多个自变量(特征)预测连续的因变量(标签)数值。它建立一条最佳拟合直线(或超平面),使得实际数据点与预测值之间的差异(即损失)最小化。常用方法包括最小二乘法。
英文出自wiki
Linear regression is a statistical method used to model the relationship between a dependent variable ( y 𝑦 ) and one or more independent variables ( x 𝑥 ) by fitting a straight line, typically using the least-squares method. It predicts outcomes, such as sales or prices, by minimizing the vertical distances between data points and the line.
1-1 学习目标
标准 1
你能说清楚线性回归是干什么的。
标准 2
你能看懂这个式子
标准 3
你知道 fit 本质是在找让误差变小的参数。
标准 4
你知道最小二乘是『让误差平方和最小』。
标准 5
你会用代码训练、预测、看系数、看评估指标。
标准 6
你知道正规方程和梯度下降都是『求参数的方法』,但你不一定要会完整证明。
2 完整代码
下面是通过学生的学习时长,出勤率和作业完成率,来判断考试分数的问题。
import warnings
warnings.filterwarnings("ignore") # 忽略一些不影响学习的警告信息
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# sklearn 自带的糖尿病回归数据集
from sklearn.datasets import load_diabetes
# 数据集切分、交叉验证
from sklearn.model_selection import train_test_split, cross_val_score, KFold
# Pipeline: 把“预处理 + 模型”串起来
from sklearn.pipeline import Pipeline
# 标准化
from sklearn.preprocessing import StandardScaler
# 线性回归模型
from sklearn.linear_model import LinearRegression
# 回归评估指标
from sklearn.metrics import (
mean_absolute_error,
mean_squared_error,
r2_score
)
# 固定随机种子,保证每次切分数据结果一致,方便复现
RANDOM_STATE = 42
def evaluate_regression(y_true, y_pred, dataset_name="dataset"):
"""
这个函数专门用来评估回归模型效果
输入:
y_true: 真实值
y_pred: 预测值
dataset_name: 当前评估的数据集名字(如 Validation / Test)
输出:
返回一个字典,里面包含多个指标
"""
# 平均绝对误差:平均每次大概错多少
mae = mean_absolute_error(y_true, y_pred)
# 均方误差:误差平方后再平均
mse = mean_squared_error(y_true, y_pred)
# RMSE:对 MSE 开根号,单位和原目标值一致,更直观
rmse = np.sqrt(mse)
# R²:拟合优度,越接近 1 越好
r2 = r2_score(y_true, y_pred)
print(f"===== {dataset_name} Evaluation =====")
print(f"MAE : {mae:.4f}")
print(f"MSE : {mse:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"R² : {r2:.4f}")
print()
# 返回结果,后面可以继续使用
return {
"mae": mae,
"mse": mse,
"rmse": rmse,
"r2": r2
}
def plot_residuals(y_true, y_pred):
"""
画残差图(Residual Plot)
残差 = 真实值 - 预测值
作用:
看模型误差有没有明显模式
如果残差随机分布在 0 附近,通常比较理想
"""
residuals = y_true - y_pred
plt.figure(figsize=(8, 5))
plt.scatter(y_pred, residuals, alpha=0.7)
plt.axhline(y=0, linestyle="--") # 画一条 y=0 的参考线
plt.xlabel("Predicted Value")
plt.ylabel("Residual (True - Predicted)")
plt.title("Residual Plot")
plt.tight_layout()
plt.show()
def plot_true_vs_pred(y_true, y_pred):
"""
画真实值 vs 预测值
如果模型效果好,点会比较接近对角线
"""
plt.figure(figsize=(6, 6))
plt.scatter(y_true, y_pred, alpha=0.7)
# 找到横纵坐标的最小值和最大值,用来画对角线
min_val = min(np.min(y_true), np.min(y_pred))
max_val = max(np.max(y_true), np.max(y_pred))
# 理想情况下,预测值=真实值,所以画一条 y=x 的参考线
plt.plot([min_val, max_val], [min_val, max_val], linestyle="--")
plt.xlabel("True Value")
plt.ylabel("Predicted Value")
plt.title("True vs Predicted")
plt.tight_layout()
plt.show()
def main():
# =========================================================
# 1. 读取数据
# =========================================================
# load_diabetes(as_frame=True) 会直接返回 pandas DataFrame 格式数据
diabetes = load_diabetes(as_frame=True)
# diabetes.frame 里包含:
# - 所有特征列
# - target 目标列
df = diabetes.frame.copy()
print("===== Raw Data Preview =====")
print(df.head()) # 看前5行
print()
# =========================================================
# 2. 拆分特征 X 和目标 y
# =========================================================
# X: 所有输入特征
# y: 目标值(我们要预测的连续数值)
X = df.drop(columns=["target"])
y = df["target"]
print("===== Feature Columns =====")
print(list(X.columns)) # 打印所有特征名
print()
print(f"X shape: {X.shape}") # 特征矩阵形状: (样本数, 特征数)
print(f"y shape: {y.shape}") # 目标向量形状: (样本数,)
print()
# =========================================================
# 3. 先拆出最终测试集
# =========================================================
# 生产/项目里,测试集一般只在最后使用一次
# 避免提前“偷看”测试集,导致评估不真实
X_train_val, X_test, y_train_val, y_test = train_test_split(
X, y,
test_size=0.2, # 20% 做最终测试集
random_state=RANDOM_STATE
)
# =========================================================
# 4. 再从 train_val 里切出验证集
# =========================================================
# 最终比例大致是:
# train = 60%
# valid = 20%
# test = 20%
X_train, X_valid, y_train, y_valid = train_test_split(
X_train_val, y_train_val,
test_size=0.25, # train_val 的 25% -> 占总数据 20%
random_state=RANDOM_STATE
)
print("===== Data Split =====")
print("Train:", X_train.shape, y_train.shape)
print("Valid:", X_valid.shape, y_valid.shape)
print("Test :", X_test.shape, y_test.shape)
print()
# =========================================================
# 5. 建立 Pipeline
# =========================================================
# Pipeline 的好处:
# 1) 把预处理和模型串起来
# 2) 防止数据泄漏
# 3) 代码更整洁
pipeline = Pipeline([
# 先做标准化
("scaler", StandardScaler()),
# 再训练线性回归模型
("model", LinearRegression())
])
# =========================================================
# 6. 在训练集上训练模型
# =========================================================
# 本质:
# 在训练数据上学习最合适的 coef_ 和 intercept_
pipeline.fit(X_train, y_train)
# =========================================================
# 7. 在验证集上预测并评估
# =========================================================
y_valid_pred = pipeline.predict(X_valid)
valid_metrics = evaluate_regression(
y_valid, y_valid_pred, dataset_name="Validation"
)
# =========================================================
# 8. 做交叉验证
# =========================================================
# 这里不在 test 上做,而是在 train_val 上做
# 作用:
# 看模型在不同数据切分下是否稳定
cv = KFold(
n_splits=5, # 5折交叉验证
shuffle=True, # 先打乱
random_state=RANDOM_STATE
)
# scoring="r2" 表示每一折用 R² 来评估
cv_scores = cross_val_score(
pipeline,
X_train_val,
y_train_val,
cv=cv,
scoring="r2"
)
print("===== Cross Validation (R²) =====")
print("Each fold:", np.round(cv_scores, 4))
print("Mean R² :", np.mean(cv_scores).round(4))
print("Std R² :", np.std(cv_scores).round(4))
print()
# =========================================================
# 9. 用 train+valid 重新训练最终模型
# =========================================================
# 原因:
# 既然模型流程已经确认了,就可以把更多数据拿来训练
final_pipeline = Pipeline([
("scaler", StandardScaler()),
("model", LinearRegression())
])
final_pipeline.fit(X_train_val, y_train_val)
# =========================================================
# 10. 在最终测试集上做评估
# =========================================================
# 注意:
# test 集只在这里出现一次
y_test_pred = final_pipeline.predict(X_test)
test_metrics = evaluate_regression(
y_test, y_test_pred, dataset_name="Test"
)
# =========================================================
# 11. 查看线性回归学到的系数
# =========================================================
# 从 pipeline 中把真正的模型对象取出来
model = final_pipeline.named_steps["model"]
# 把特征名和系数对应起来,方便阅读
coef_df = pd.DataFrame({
"feature": X.columns,
"coefficient": model.coef_
})
# 按绝对值大小排序,便于看哪个特征影响更大
coef_df = coef_df.sort_values(
by="coefficient",
key=np.abs,
ascending=False
)
print("===== Coefficients =====")
print(coef_df)
print()
print(f"Intercept: {model.intercept_:.4f}")
print()
# =========================================================
# 12. 查看部分预测结果
# =========================================================
result_df = pd.DataFrame({
"true_value": y_test.values,
"predicted_value": y_test_pred,
"residual": y_test.values - y_test_pred
})
print("===== Prediction Sample =====")
print(result_df.head(10)) # 只看前10条
print()
# =========================================================
# 13. 残差分析
# =========================================================
plot_residuals(y_test, y_test_pred)
plot_true_vs_pred(y_test, y_test_pred)
# =========================================================
# 14. 对新样本做预测
# =========================================================
# 这里为了方便,直接拿测试集前3条当“新样本”
new_samples = X_test.iloc[:3].copy()
new_preds = final_pipeline.predict(new_samples)
print("===== New Sample Prediction =====")
for i, pred in enumerate(new_preds, start=1):
print(f"Sample {i} predicted target: {pred:.4f}")
print()
# =========================================================
# 15. 简单打印最终摘要
# =========================================================
print("===== Final Summary =====")
print(f"Validation R²: {valid_metrics['r2']:.4f}")
print(f"Test R² : {test_metrics['r2']:.4f}")
# Python 脚本入口
if __name__ == "__main__":
main()
Q1:如何理解MAE,RMSE,R²?
你可以把这三个指标理解成:从三个角度看模型到底准不准。背后是数学推导出来的。如果数学不好,可以直接记忆下面的结论。毕竟都前辈们总结出来的经验。
MAE = 平均每次大概错多少。大白话就是 模型平均每次会偏离真实值多少
- 真实房价 100 万,你预测 90 万,错了 10 万
- 真实房价 200 万,你预测 220 万,错了 20 万
RMSE = 也是看平均错多少,但更讨厌大错。RMSE 也表示平均误差,但它对『错得特别离谱』的情况更敏感
- MAE:每次错多少,直接平均
- RMSE:先把大的错误放大,再平均
假设两个模型:
- 模型 A:大多数时候都小错
- 模型 B:平时也还行,但偶尔会错特别大
这时候MAE 可能看起来差不多,但是RMSE 往往会把模型 B 判得更差。
R² = 这个模型到底有没有学到规律,模型对数据规律解释得怎么样。
R² = 1 最好,几乎完美预测
R² = 0 和乱猜平均值差不多
R² < 0 比乱猜平均值还差
举一个例子平均差几分,但如果有些学生被你错得特别.
- MAE:平均错多少
- RMSE:平均错多少,但更怕大错
- R²:模型有没有学到规律,越接近 1 越好
Q2:为什么需要那两个函数plot_residuals 和plot_true_vs_pred
因为单看一个分数不够。MAE,RMSE,R²。这些指标只能告诉你:整体上好不好。但是无法看出来,错误整体的规律。所以你才要继续看下面的2个图。
- 模型是不是系统性偏大或偏小
- 线性回归的假设有没有被破坏
plot_true_vs_pred 表示 预测值(predicted value)和真实值(true value)是不是接近,如果模型很好,点应该尽量靠近那条对角线 y = x ,因为这表示 真实值 = 预测值 。他的含义就是
- 模型整体预测得准不准
- 是否在高值区预测偏低
- 是否在低值区预测偏高
- 是否有明显离群点(outlier)
例如你发现,小值预测还行,大值总是预测偏低,那说明模型可能没有把大值区间学好。
plot_residuals 表示残差(residual)就是 真实值减预测值,这个图是在看误差有没有模式
如果线性回归比较合适,残差通常应该 1️⃣围绕 0 上下随机分布 2️⃣没有明显形状。它的意义就是
- 模型是不是系统性偏高或偏低
- 误差是不是随着预测值变大而变大
- 是否存在非线性关系(nonlinearity)
- 是否有异方差(heteroscedasticity)
大白话就是
- plot_true_vs_pred:看预测结果和真实值贴不贴近
- plot_residuals:看误差是不是随机、有没有异常模式
Q3: 下面这段代码如何理解?
下面仔细说一下
cv = KFold(
n_splits=5, # 5 折交叉验证
shuffle=True, # 在切 5 份之前,先把数据打乱。这样更随机,不容易因为原数据顺序问题导致切分不均匀。
random_state=RANDOM_STATE # 固定随机种子 这样每次运行时,打乱方式一致。作用是:结果可复现(reproducible)
)
这里是在定义一种『怎么切数据』的规则。
n_splits=5 也就是 5 折交叉验证(5-fold cross validation)。
- 把数据分成 5 份
- 轮流拿 1 份做验证集
- 剩下 4 份做训练集
结果就是
- 每轮 80 条训练
- 20 条验证
- 总共做 5 轮
cv_scores = cross_val_score(
pipeline,
X_train_val,
y_train_val,
cv=cv, # 按你前面定义的 KFold(n_splits=5, ...) 来切
scoring="r2" # 每一折评估时,用的是 R²。最后拿到的 5 个 R² 分数。每个分数对应一折。
)
- 用 pipeline 这个模型流程
- 在
X_train_val, y_train_val这批数据上 - 按刚才定义的 cv 规则做 5 折交叉验证
- 每一折都用 R² 来评分
关于pipeline 不仅有回归,还会有标准化。
StandardScaler()LinearRegression()
每一折里,都会先做标准化,再训练线性回归
X_train_val, y_train_val 这里用的是训练+验证这部分数据,不包括最终 test 集。因为交叉验证本来就是训练阶段内部用的。
cv_scores 是一个数组 ,表示 同一个模型,在 5 次不同切分下,各自表现如何 比如可能长这样:
[0.41, 0.52, 0.47, 0.38, 0.50]
- 第 1 折 R² = 0.41
- 第 2 折 R² = 0.52
- 第 3 折 R² = 0.47
- 第 4 折 R² = 0.38
- 第 5 折 R² = 0.50
最后打印的意义在于
print("Each fold:", np.round(cv_scores, 4)) # 这是把每一折的 R² 打印出来,并保留 4 位小数。
# 意思就是每一轮的小考试分数。
Each fold: [0.4123 0.5211 0.4738 0.3892 0.5014]
print('Mean R² :', np.mean(cv_scores).round(4)) # 这是算 5 折平均分。
# 这个模型在交叉验证里的平均 R² 大约是 0.46 这个平均值通常比单次切分更稳。
Mean R² : 0.4596
print('Std R² :', np.std(cv_scores).round(4)) # 这是算 标准差(standard deviation)。
# 如果标准差小 Std R² : 0.03 表示 5次结果都差不多 模型比较稳定
# 如果标准差大 Std R² : 0.15 表示5次有好又坏 模型不太稳定
Mean R² 表示这个模型平均效果怎么样?
Std R² 这个模型稳不稳?
比喻的话
- Each fold = 每次小考成绩
- Mean R² = 平均成绩
- Std R² = 成绩稳定性
用 5 折交叉验证,检查线性回归模型的平均表现和稳定性。
Q4: 下面这段代码呢
# 按绝对值大小排序,便于看哪个特征影响更大
coef_df = coef_df.sort_values(
by="coefficient",
key=np.abs,
ascending=False
)
-
多元线性回归是
-
每个特征都有一个自己的 w
-
w 的正负看影响方向
-
w 的绝对值大小看影响强弱
严格解释系数大小,最好建立在特征尺度可比的前提下。比如做过标准化以后,这样比较更靠谱。
结论就是把模型学到的系数做成可读表格,并按影响强弱排序,方便解释模型。