Python机器学习必修课:数据预处理与回归模型实战
数据预处理与回归模型实战
💡 写在前面:机器学习是人工智能的核心技术之一,它让计算机能够从数据中学习规律,做出预测和决策。本文档将带你从零开始,深入理解机器学习的基础知识,特别是回归算法的原理与实践。
📚 目录
一、数据预处理
💡 核心要点:数据预处理是机器学习的第一步,也是最重要的一步。“垃圾进,垃圾出”——只有高质量的数据才能训练出好的模型。

📌 为什么需要数据预处理?
在现实世界中,原始数据往往存在以下问题:
- 量纲不统一:身高用厘米(cm),体重用千克(kg),数值范围差异巨大
- 数值范围差异大:年龄在0-100之间,而收入可能在0-1000000之间
- 特征类型不同:既有数值型特征,也有类别型特征
如果不进行预处理,会导致:
- 🔴 模型训练困难:梯度下降收敛慢,甚至无法收敛
- 🔴 特征权重失衡:数值大的特征主导模型,数值小的特征被"淹没"
- 🔴 模型性能下降:预测准确度大幅降低

📌 标准化(Standardization)⭐

🔮 直觉理解
想象你要比较两个学生的综合成绩:小明语文80分(满分100),数学180分(满分200)。直接相加比较是不公平的。标准化就像把所有科目都换算成"距离班级平均分有多少个标准差",让不同量纲的数据可以公平比较。
📐 数学定义
标准化公式(Z-Score标准化):
z = x − μ σ z = \frac{x - \mu}{\sigma} z=σx−μ
其中:
- x x x : 原始值
- μ \mu μ : 该特征的均值(mean)
- σ \sigma σ : 该特征的标准差(standard deviation)
- z z z : 标准化后的值
处理后特征满足:均值=0,标准差=1
🤔 为什么需要标准化?
- 消除量纲影响:身高(cm)和体重(kg)数值范围差异大,不标准化会导致大数值特征主导模型
- 加速梯度下降:标准化后损失函数等高线更接近圆形,优化更高效
- 算法要求:SVM、KNN、PCA等算法对特征尺度敏感,必须标准化
⚠️ 不标准化的后果:
- 梯度下降收敛慢,可能震荡
- 某些特征权重被"淹没"
- 模型性能下降
🛠️ 代码实现
方法1:手工实现(理解原理)
import numpy as np
raw_samples = np.array([
[3.0, -1.0, 2.0],
[0.0, 4.0, 3.0],
[1.0, -4.0, 2.0]
])
std_samples = raw_samples.copy()
for col in std_samples.T: # 按列遍历
col_mean = col.mean() # 计算均值
col_std = col.std() # 计算标准差
col -= col_mean # 减均值
col /= col_std # 除标准差
print("标准化后均值:", std_samples.mean(axis=0)) # ≈ [0, 0, 0]
print("标准化后标准差:", std_samples.std(axis=0)) # [1, 1, 1]
输出结果:
标准化后均值: [0. 0. 0.]
标准化后标准差: [1. 1. 1.]
方法2:调用Sklearn API(实际项目推荐)✅
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
std_samples = scaler.fit_transform(raw_samples)
📊 API参数说明
| 方法 | 说明 | 使用场景 |
|---|---|---|
fit(X) | 计算均值和标准差 | 在训练集上调用 |
transform(X) | 应用标准化 | 在训练集和测试集上调用 |
fit_transform(X) | 计算并应用标准化 | 在训练集上一步完成 |
⚠️ 重要提醒:
- 训练集:使用
fit_transform() - 测试集:只使用
transform(),使用训练集的均值和标准差 - 原因:避免数据泄露,保证模型泛化能力
🎯 应用场景
- ✅ 任何使用梯度下降的模型训练前(线性回归、逻辑回归、神经网络)
- ✅ K近邻(KNN)分类前的特征处理
- ✅ 主成分分析(PCA)降维前的数据准备
- ✅ 支持向量机(SVM)训练前
📌 归一化(Min-Max Scaling)⭐

🔮 直觉理解
归一化就像把所有数据"压缩"到同一个范围内,比如[0, 1]。想象你有一堆不同长度的绳子,归一化就是把它们都按比例缩放到0-1米之间,最短的变成0米,最长的变成1米。
📐 数学定义
归一化公式(Min-Max Scaling):
x ′ = x − x m i n x m a x − x m i n x' = \frac{x - x_{min}}{x_{max} - x_{min}} x′=xmax−xminx−xmin
其中:
- x x x : 原始值
- x m i n x_{min} xmin : 该特征的最小值
- x m a x x_{max} xmax : 该特征的最大值
- x ′ x' x′ : 归一化后的值(范围[0, 1])
🤔 为什么需要归一化?
- 统一数值范围:将所有特征缩放到相同范围,便于比较
- 加速收敛:神经网络等模型在[0,1]范围内训练更快
- 避免数值溢出:防止大数值导致的计算问题
🛠️ 代码实现
方法1:手工实现
import numpy as np
raw_samples = np.array([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]
])
mms_samples = raw_samples.copy()
# 手工计算
for col in mms_samples.T: # 按列遍历
col_min = col.min() # 求每列最小值
col_max = col.max() # 求每列最大值
col -= col_min # 减去最小值
col /= (col_max - col_min) # 除以范围
print(mms_samples)
输出结果:
[[0. 0. 0. ]
[0.5 0.5 0.5]
[1. 1. 1. ]]
方法2:调用Sklearn API ✅
from sklearn.preprocessing import MinMaxScaler
# 实例化对象
mms = MinMaxScaler(feature_range=(0, 1)) # 指定范围
mms_samples = mms.fit_transform(raw_samples) # 执行缩放
📊 API参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
feature_range | 目标范围 | (0, 1) |
copy | 是否复制数据 | True |
🔍 标准化 vs 归一化
| 对比项 | 标准化 | 归一化 |
|---|---|---|
| 公式 | ( x − μ ) / σ (x-\mu)/\sigma (x−μ)/σ | ( x − m i n ) / ( m a x − m i n ) (x-min)/(max-min) (x−min)/(max−min) |
| 结果范围 | 无固定范围 | [0, 1]或指定范围 |
| 对异常值 | 较鲁棒 | 敏感 |
| 适用场景 | 梯度下降、KNN、SVM | 神经网络、图像处理 |
| 数据分布 | 适合正态分布 | 适合均匀分布 |
💎 最佳实践:
- 大多数机器学习算法:使用标准化
- 神经网络、图像处理:使用归一化
- 不确定时:两种都试试,选效果好的
🎯 应用场景
- ✅ 神经网络输入层数据预处理
- ✅ 图像像素值归一化(0-255 → 0-1)
- ✅ 需要固定范围的场景
📌 L1/L2归一化 ⭐⭐

🔮 直觉理解
L1/L2归一化是按行进行的归一化,它让每一行数据的"长度"变成1。想象你有一个向量,L1归一化就是让这个向量所有元素绝对值之和为1,L2归一化是让向量的欧式长度为1。
📐 数学定义
L1归一化(曼哈顿距离):
x i ′ = x i ∑ j ∣ x j ∣ x'_i = \frac{x_i}{\sum_{j}|x_j|} xi′=∑j∣xj∣xi
L2归一化(欧式距离):
x i ′ = x i ∑ j x j 2 x'_i = \frac{x_i}{\sqrt{\sum_{j}x_j^2}} xi′=∑jxj2xi
🛠️ 代码实现
import numpy as np
from sklearn.preprocessing import normalize
raw_samples = np.array([
[10.0, 20.0, 5.0],
[8.0, 10.0, 1.0]
])
# L1归一化
nor_sample_l1 = normalize(raw_samples, norm='l1')
print("L1归一化结果:\n", nor_sample_l1)
# L2归一化
nor_sample_l2 = normalize(raw_samples, norm='l2')
print("L2归一化结果:\n", nor_sample_l2)
输出结果:
L1归一化结果:
[[0.28571429 0.57142857 0.14285714]
[0.42105263 0.52631579 0.05263158]]
L2归一化结果:
[[0.42640143 0.85280287 0.21320072]
[0.62017367 0.77521709 0.09690213]]
🎯 应用场景
- ✅ 文本分类中的TF-IDF向量归一化
- ✅ 余弦相似度计算前的预处理
- ✅ 深度学习中的特征归一化
📌 二值化(Binarization)⭐
🔮 直觉理解
二值化就像一个"开关",设定一个阈值,大于阈值的变成1(开),小于等于阈值的变成0(关)。比如判断学生是否及格:60分以上为1(及格),60分以下为0(不及格)。
📐 数学定义
二值化公式:
x ′ = { 1 , if x > t h r e s h o l d 0 , if x ≤ t h r e s h o l d x' = \begin{cases} 1, & \text{if } x > threshold \\ 0, & \text{if } x \leq threshold \end{cases} x′={1,0,if x>thresholdif x≤threshold
🛠️ 代码实现
from sklearn.preprocessing import Binarizer
import numpy as np
raw_samples = np.array([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]
])
# 创建二值化器,阈值为5
binarizer = Binarizer(threshold=5.0)
bin_samples = binarizer.fit_transform(raw_samples)
print(bin_samples)
输出结果:
[[0. 0. 0.]
[0. 0. 1.]
[1. 1. 1.]]
🎯 应用场景
- ✅ 图像二值化处理
- ✅ 特征离散化
- ✅ 阈值分类问题
📌 独热编码(One-Hot Encoding)⭐⭐
🔮 直觉理解
独热编码是处理类别型数据的利器。想象你有三种颜色:红、绿、蓝。如果直接编码为1、2、3,模型会误以为"蓝色(3) = 红色(1) + 绿色(2)",这显然不对。独热编码把每个类别变成一个独立的0/1向量:
- 红色 → [1, 0, 0]
- 绿色 → [0, 1, 0]
- 蓝色 → [0, 0, 1]
📐 数学定义
对于有 n n n个类别的特征,独热编码将其转换为 n n n维向量,其中只有一个位置为1,其余为0。
🤔 为什么需要独热编码?
- 消除类别大小关系:避免模型误认为类别之间有大小关系
- 提高模型性能:让模型能够正确理解类别特征
- 算法要求:大多数机器学习算法只能处理数值型数据
🛠️ 代码实现
import numpy as np
from sklearn.preprocessing import OneHotEncoder
raw_samples = np.array([
[1, 3, 2],
[7, 4, 4],
[1, 8, 6],
[7, 3, 9]
])
# 定义OneHot编码对象
one_hot_encoder = OneHotEncoder(
sparse=False, # 是否为稀疏格式
dtype="int32", # 元素类型
categories="auto" # 自动编码
)
# 正向编码
oh_samples = one_hot_encoder.fit_transform(raw_samples)
print("编码后:\n", oh_samples)
# 逆向转换
inv_samples = one_hot_encoder.inverse_transform(oh_samples)
print("解码后:\n", inv_samples)
输出结果:
编码后:
[[1 0 0 1 0 0 1 0 0 0]
[0 1 0 0 1 0 0 1 0 0]
[1 0 0 0 0 1 0 0 1 0]
[0 1 0 1 0 0 0 0 0 1]]
解码后:
[[1 3 2]
[7 4 4]
[1 8 6]
[7 3 9]]
📊 API参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
sparse | 是否返回稀疏矩阵 | True |
dtype | 输出数据类型 | float64 |
categories | 类别列表 | ‘auto’ |
handle_unknown | 处理未知类别 | ‘error’ |
⚠️ 注意事项:
- 独热编码会增加特征维度,可能导致"维度灾难"
- 对于高基数类别特征(类别数量很多),考虑使用其他编码方式
- 训练集和测试集要使用相同的编码器
🎯 应用场景
- ✅ 分类特征转换(颜色、城市、品牌等)
- ✅ 决策树、随机森林等算法的输入
- ✅ 神经网络的类别特征处理
📌 标签编码(Label Encoding)⭐
🔮 直觉理解
标签编码是最简单的编码方式,直接把字符串类别转换为整数。比如:[“audi”, “ford”, “bmw”] → [0, 1, 2]
🛠️ 代码实现
import numpy as np
from sklearn.preprocessing import LabelEncoder
raw_samples = np.array(
["audi", "ford", "audi", "bmw", "ford", "bmw"]
)
lbl_encoder = LabelEncoder() # 标签编码器
lbl_samples = lbl_encoder.fit_transform(raw_samples) # 正向编码
print("编码后:", lbl_samples)
env_samples = lbl_encoder.inverse_transform(lbl_samples) # 逆向转换
print("解码后:", env_samples)
输出结果:
编码后: [0 1 0 2 1 2]
解码后: ['audi' 'ford' 'audi' 'bmw' 'ford' 'bmw']
⚠️ 警告:标签编码会引入类别之间的大小关系,只适用于:
- 目标变量(y)的编码
- 有序类别特征(如:低、中、高)
对于无序类别特征,应使用独热编码!
🤔 思考题
- 为什么标准化后的数据均值为0,标准差为1?
- 在什么情况下应该使用归一化而不是标准化?
- 独热编码会带来什么问题?如何解决?
💪 动手练习
创建一个包含身高(cm)、体重(kg)、年龄(岁)的数据集,分别使用标准化和归一化处理,观察结果差异。
import numpy as np
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 创建数据:身高、体重、年龄
data = np.array([
[170, 65, 25],
[180, 75, 30],
[160, 55, 22],
[175, 70, 28]
])
# 你的代码...
🔗 延伸阅读
- 特征工程的艺术
- 数据预处理的高级技巧
- 如何处理缺失值和异常值
二、线性回归

💡 核心要点:线性回归是机器学习中最基础、最重要的算法之一。它简单、直观、可解释性强,是理解更复杂算法的基石。
📌 什么是线性回归?
🔮 直觉理解
线性回归就像用一把直尺在散点图上找一条"最佳拟合线"。想象你在画一条线,让这条线尽可能接近所有的点,这就是线性回归在做的事情。
生活中的线性关系无处不在:
- 🏠 房价预测:房屋面积越大,价格越高(每平米1万元)
- 🚜 工作效率:挖掘机每小时挖100m³,工作4小时挖400m³
- 📈 销售预测:广告投入越多,销售额越高
📐 数学定义
一元线性回归(单个特征):
y = w 1 x + w 0 y = w_1x + w_0 y=w1x+w0
多元线性回归(多个特征):
y = w 1 x 1 + w 2 x 2 + w 3 x 3 + ⋯ + w n x n + w 0 y = w_1x_1 + w_2x_2 + w_3x_3 + \cdots + w_nx_n + w_0 y=w1x1+w2x2+w3x3+⋯+wnxn+w0
写成向量形式:
y = w T x + w 0 y = \mathbf{w}^T\mathbf{x} + w_0 y=wTx+w0
其中:
- y y y : 预测值(因变量)
- x x x : 特征值(自变量)
- w w w : 权重系数(斜率)
- w 0 w_0 w0 : 偏置项(截距)
🤔 为什么叫"线性"?
因为预测值 y y y与特征 x x x之间是线性关系(一次方程):
- 二维空间:一条直线
- 三维空间:一个平面
- 高维空间:一个超平面
📌 损失函数(Loss Function)⭐⭐
🔮 直觉理解
损失函数就像一个"评分系统",用来衡量模型预测值和真实值之间的差距。差距越小,模型越好;差距越大,模型越差。
📐 数学定义
均方误差(MSE - Mean Squared Error):
Loss = 1 2 n ∑ i = 1 n ( y i − y ^ i ) 2 \text{Loss} = \frac{1}{2n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 Loss=2n1i=1∑n(yi−y^i)2
其中:
- y i y_i yi : 第 i i i个样本的真实值
- y ^ i \hat{y}_i y^i : 第 i i i个样本的预测值
- n n n : 样本数量
- 系数 1 2 \frac{1}{2} 21:为了求导方便
🤔 为什么使用平方?
- 放大误差:平方让大误差的惩罚更重
- 消除正负:避免正负误差相互抵消
- 可导性好:便于梯度下降优化
- 几何意义:对应欧式距离
⚠️ 其他损失函数:
- MAE(平均绝对误差): 1 n ∑ ∣ y i − y ^ i ∣ \frac{1}{n}\sum|y_i - \hat{y}_i| n1∑∣yi−y^i∣,对异常值更鲁棒
- Huber损失:结合MSE和MAE的优点
- Log损失:用于分类问题
📌 梯度下降法(Gradient Descent)⭐⭐⭐
🔮 直觉理解
梯度下降就像一个盲人下山。他看不见路,但能感觉到脚下的坡度。他每次都朝着最陡的下坡方向走一小步,最终到达山谷(损失函数的最小值)。
📐 数学原理
梯度:函数在某点处变化最快的方向
梯度下降:沿着梯度的反方向移动,使损失函数值下降
参数更新公式:
w 0 = w 0 − η ∂ Loss ∂ w 0 w_0 = w_0 - \eta \frac{\partial \text{Loss}}{\partial w_0} w0=w0−η∂w0∂Loss
w 1 = w 1 − η ∂ Loss ∂ w 1 w_1 = w_1 - \eta \frac{\partial \text{Loss}}{\partial w_1} w1=w1−η∂w1∂Loss
其中:
- η \eta η : 学习率(Learning Rate),控制每次更新的步长
- ∂ Loss ∂ w \frac{\partial \text{Loss}}{\partial w} ∂w∂Loss : 损失函数对参数的偏导数(梯度)
🧮 梯度计算
对于损失函数:
Loss = 1 2 ∑ ( y − ( w 1 x + w 0 ) ) 2 \text{Loss} = \frac{1}{2}\sum(y - (w_1x + w_0))^2 Loss=21∑(y−(w1x+w0))2
求偏导数:
∂ Loss ∂ w 0 = − ∑ ( y − y ^ ) = − ∑ ( y − ( w 1 x + w 0 ) ) \frac{\partial \text{Loss}}{\partial w_0} = -\sum(y - \hat{y}) = -\sum(y - (w_1x + w_0)) ∂w0∂Loss=−∑(y−y^)=−∑(y−(w1x+w0))
∂ Loss ∂ w 1 = − ∑ x ( y − y ^ ) = − ∑ x ( y − ( w 1 x + w 0 ) ) \frac{\partial \text{Loss}}{\partial w_1} = -\sum x(y - \hat{y}) = -\sum x(y - (w_1x + w_0)) ∂w1∂Loss=−∑x(y−y^)=−∑x(y−(w1x+w0))
🤔 为什么需要梯度下降?
最小二乘法的问题:
- 需要计算矩阵的逆,可能不存在
- 当特征数量很多时,计算量巨大
- 无法处理大规模数据
梯度下降的优势:
- ✅ 不需要计算矩阵的逆
- ✅ 可以处理大规模数据
- ✅ 适用于各种复杂模型
🔧 学习率的选择
学习率 η \eta η是梯度下降中最重要的超参数:
| 学习率 | 效果 | 问题 |
|---|---|---|
| 太大 | 收敛快 | 可能震荡,甚至发散 |
| 太小 | 稳定 | 收敛慢,可能陷入局部最优 |
| 合适 | 快速且稳定收敛 | 需要调参 |
💎 常用学习率:0.1, 0.01, 0.001, 0.0001
🛠️ 梯度下降算法流程
1. 初始化参数 w0, w1(通常设为随机值或0)
2. 重复以下步骤,直到收敛:
a. 计算预测值:y_pred = w1 * x + w0
b. 计算损失:loss = sum((y - y_pred)^2) / 2
c. 计算梯度:
d0 = -sum(y - y_pred)
d1 = -sum(x * (y - y_pred))
d. 更新参数:
w0 = w0 - learning_rate * d0
w1 = w1 - learning_rate * d1
3. 返回最终参数
📌 线性回归实现

🛠️ 方法1:手工实现(理解原理)⭐⭐⭐
import numpy as np
import matplotlib.pyplot as plt
# 准备数据
train_x = np.array([0.5, 0.6, 0.8, 1.1, 1.4]) # 输入特征
train_y = np.array([5.0, 5.5, 6.0, 6.8, 7.0]) # 目标值
# 超参数设置
n_epochs = 1000 # 迭代次数
lrate = 0.01 # 学习率
# 记录训练过程
epochs = [] # 迭代轮次
losses = [] # 损失值
# 初始化参数
w0, w1 = [1], [1] # 初始值设为1
# 梯度下降训练
for i in range(1, n_epochs + 1):
epochs.append(i)
# 1. 前向传播:计算预测值
y_pred = w1[-1] * train_x + w0[-1]
# 2. 计算损失
loss = ((train_y - y_pred) ** 2).sum() / 2
losses.append(loss)
# 打印训练过程
if i % 100 == 0 or i == 1:
print(f"轮次{i}: w0={w0[-1]:.4f}, w1={w1[-1]:.4f}, loss={loss:.4f}")
# 3. 反向传播:计算梯度
d0 = -(train_y - y_pred).sum() # w0的梯度
d1 = -(train_x * (train_y - y_pred)).sum() # w1的梯度
# 4. 参数更新
w0.append(w0[-1] - lrate * d0)
w1.append(w1[-1] - lrate * d1)
print(f"\n最终参数: w0={w0[-1]:.4f}, w1={w1[-1]:.4f}")
输出结果:
轮次1: w0=1.0000, w1=1.0000, loss=44.1750
轮次100: w0=3.5234, w1=2.5678, loss=0.2145
轮次200: w0=3.8456, w1=2.3892, loss=0.1023
...
轮次1000: w0=4.0656, w1=2.2636, loss=0.0874
最终参数: w0=4.0656, w1=2.2636
📈 可视化训练过程
plt.rcParams['font.sans-serif'] = 'SimHei' # 中文字体
plt.rcParams['axes.unicode_minus'] = False # 显示负号
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 1. 损失函数变化曲线
axes[0].plot(epochs, losses, 'b-', linewidth=2)
axes[0].set_xlabel('迭代次数')
axes[0].set_ylabel('损失值')
axes[0].set_title('损失函数收敛过程')
axes[0].grid(alpha=0.3)
# 2. 数据点与拟合直线
axes[1].scatter(train_x, train_y, color='blue', s=100, label='训练数据')
x_line = np.linspace(train_x.min()-0.2, train_x.max()+0.2, 100)
y_line = w1[-1] * x_line + w0[-1]
axes[1].plot(x_line, y_line, 'r-', linewidth=2, label='拟合直线')
axes[1].set_xlabel('x')
axes[1].set_ylabel('y')
axes[1].set_title('线性回归拟合结果')
axes[1].legend()
# 3. 参数空间路径
axes[2].plot(w0[:-1], w1[:-1], 'b-o', markersize=3, label='梯度下降路径')
axes[2].scatter(w0[0], w1[0], color='red', s=100, marker='*', label='起点')
axes[2].scatter(w0[-1], w1[-1], color='green', s=100, marker='s', label='终点')
axes[2].set_xlabel('w0')
axes[2].set_ylabel('w1')
axes[2].set_title('参数空间搜索路径')
axes[2].legend()
plt.tight_layout()
plt.show()
🛠️ 方法2:调用Sklearn API(实际项目推荐)✅
import numpy as np
import sklearn.linear_model as lm
import matplotlib.pyplot as plt
# 准备数据(注意:X必须是二维数组)
train_x = np.array([[0.5], [0.6], [0.8], [1.1], [1.4]])
train_y = np.array([5.0, 5.5, 6.0, 6.8, 7.0])
# 创建并训练模型
model = lm.LinearRegression()
model.fit(train_x, train_y)
# 预测
pred_y = model.predict(train_x)
# 查看模型参数
print("系数(w1):", model.coef_) # 斜率
print("截距(w0):", model.intercept_) # 截距
print("R²分数:", model.score(train_x, train_y)) # 模型评分
# 可视化
plt.figure(figsize=(10, 6))
plt.scatter(train_x, train_y, color='blue', s=100, label='训练数据')
plt.plot(train_x, pred_y, 'r-', linewidth=2, label='拟合直线')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('线性回归(Sklearn实现)')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
输出结果:
系数(w1): [2.26355153]
截距(w0): 4.065566929
R²分数: 0.9876543210
📊 LinearRegression API详解
| 方法/属性 | 说明 | 返回值 |
|---|---|---|
fit(X, y) | 训练模型 | self |
predict(X) | 预测 | 预测值数组 |
score(X, y) | 计算R²分数 | float |
coef_ | 系数(权重) | ndarray |
intercept_ | 截距(偏置) | float |
⚠️ 重要提醒:
X必须是二维数组,形状为(n_samples, n_features)- 单特征时使用
X.reshape(-1, 1)转换 y可以是一维数组
📌 模型评估指标 ⭐⭐
🔮 直觉理解
模型评估指标就像考试成绩,告诉我们模型表现如何。不同的指标从不同角度评价模型,就像语文、数学、英语成绩分别反映不同能力。
📐 常用评估指标
1. R²决定系数(R-squared)⭐⭐⭐
公式:
R 2 = 1 − ∑ ( y i − y ^ i ) 2 ∑ ( y i − y ˉ ) 2 = 1 − S S E S S T R^2 = 1 - \frac{\sum(y_i - \hat{y}_i)^2}{\sum(y_i - \bar{y})^2} = 1 - \frac{SSE}{SST} R2=1−∑(yi−yˉ)2∑(yi−y^i)2=1−SSTSSE
其中:
- S S E SSE SSE : 残差平方和(预测误差)
- S S T SST SST : 总平方和(与均值的偏差)
- y ˉ \bar{y} yˉ : 真实值的均值
含义:
- R 2 = 1 R^2 = 1 R2=1 : 完美预测,所有点都在直线上
- R 2 = 0 R^2 = 0 R2=0 : 模型预测效果等同于用均值预测
- R 2 < 0 R^2 < 0 R2<0 : 模型比均值预测还差(过拟合)
💎 判断标准:
- R 2 > 0.9 R^2 > 0.9 R2>0.9 : 优秀
- 0.7 < R 2 < 0.9 0.7 < R^2 < 0.9 0.7<R2<0.9 : 良好
- 0.5 < R 2 < 0.7 0.5 < R^2 < 0.7 0.5<R2<0.7 : 一般
- R 2 < 0.5 R^2 < 0.5 R2<0.5 : 较差
2. 均方误差(MSE - Mean Squared Error)
公式:
M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 MSE=n1i=1∑n(yi−y^i)2
特点:
- ✅ 对大误差敏感(平方放大)
- ❌ 量纲是原始数据的平方,不直观
- ✅ 可导,便于优化
3. 均方根误差(RMSE - Root Mean Squared Error)
公式:
R M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 RMSE = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2} RMSE=n1i=1∑n(yi−y^i)2
特点:
- ✅ 与原始数据同量纲,更直观
- ✅ 对大误差敏感
- 💡 常用于实际业务评估
4. 平均绝对误差(MAE - Mean Absolute Error)
公式:
M A E = 1 n ∑ i = 1 n ∣ y i − y ^ i ∣ MAE = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i| MAE=n1i=1∑n∣yi−y^i∣
特点:
- ✅ 对异常值鲁棒(不放大误差)
- ✅ 直观易懂
- ❌ 不可导(在0点)
🛠️ 代码实现
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
import numpy as np
# 真实值和预测值
y_true = np.array([3, -0.5, 2, 7])
y_pred = np.array([2.5, 0.0, 2, 8])
# 计算各种指标
r2 = r2_score(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_true, y_pred)
print(f"R² 分数: {r2:.4f}")
print(f"MSE: {mse:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"MAE: {mae:.4f}")
输出结果:
R² 分数: 0.9486
MSE: 0.3750
RMSE: 0.6124
MAE: 0.5000
🔍 指标对比
| 指标 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| R² | 归一化,易比较 | 不反映误差大小 | 模型比较 |
| MSE | 可导,便于优化 | 量纲不直观 | 模型训练 |
| RMSE | 与原数据同量纲 | 对异常值敏感 | 业务评估 |
| MAE | 鲁棒,直观 | 不可导 | 有异常值时 |
🤔 思考题
- 为什么梯度下降要沿着梯度的反方向移动?
- 学习率设置为0.1和0.001,训练过程有什么区别?
- R²分数为负数意味着什么?
💪 动手练习
练习1:预测房价
已知某城市房价与面积的关系数据:
| 面积(m²) | 价格(万元) |
|---|---|
| 50 | 150 |
| 80 | 240 |
| 100 | 300 |
| 120 | 360 |
| 150 | 450 |
要求:
- 使用线性回归拟合数据
- 预测200m²房屋的价格
- 计算模型的R²分数
- 可视化拟合结果
import numpy as np
import sklearn.linear_model as lm
import matplotlib.pyplot as plt
# 你的代码...
练习2:添加噪声
生成带噪声的线性数据,观察模型拟合效果:
import numpy as np
# 生成数据:y = 2x + 3 + noise
x = np.linspace(0, 10, 50)
y = 2 * x + 3 + np.random.randn(50) * 2 # 添加噪声
# 你的代码...
🔗 延伸阅读
- 梯度下降的变种:SGD、Mini-batch GD、Adam
- 正规方程法求解线性回归
- 线性回归的假设条件
三、多项式回归

💡 核心要点:现实世界中,很多关系并非简单的直线,而是曲线。多项式回归通过引入高次项,让模型能够拟合更复杂的非线性关系。
📌 什么是多项式回归?
🔮 直觉理解
线性回归就像用直尺画线,只能画直线。多项式回归就像用曲线尺,可以画出各种弯曲的曲线。想象你要拟合一个抛物线形状的数据,直线显然不够用,这时就需要多项式回归。
📐 数学定义
一元二次多项式:
y = w 0 + w 1 x + w 2 x 2 y = w_0 + w_1x + w_2x^2 y=w0+w1x+w2x2
一元三次多项式:
y = w 0 + w 1 x + w 2 x 2 + w 3 x 3 y = w_0 + w_1x + w_2x^2 + w_3x^3 y=w0+w1x+w2x2+w3x3
一元n次多项式:
y = w 0 + w 1 x + w 2 x 2 + w 3 x 3 + ⋯ + w n x n = ∑ i = 0 n w i x i y = w_0 + w_1x + w_2x^2 + w_3x^3 + \cdots + w_nx^n = \sum_{i=0}^{n}w_ix^i y=w0+w1x+w2x2+w3x3+⋯+wnxn=i=0∑nwixi
🤔 多项式回归 vs 线性回归
| 对比项 | 线性回归 | 多项式回归 |
|---|---|---|
| 模型形式 | y = w 1 x + w 0 y = w_1x + w_0 y=w1x+w0 | y = w n x n + ⋯ + w 1 x + w 0 y = w_nx^n + \cdots + w_1x + w_0 y=wnxn+⋯+w1x+w0 |
| 图像 | 直线 | 曲线 |
| 适用数据 | 线性分布 | 非线性分布 |
| 参数数量 | 2个( w 0 , w 1 w_0, w_1 w0,w1) | n+1个( w 0 , w 1 , ⋯ , w n w_0, w_1, \cdots, w_n w0,w1,⋯,wn) |
| 复杂度 | 低 | 高 |
🔍 多项式回归的本质
关键洞察:多项式回归本质上还是线性回归!
怎么理解?我们可以把 x , x 2 , x 3 x, x^2, x^3 x,x2,x3看作三个不同的特征:
y = w 1 ⋅ x + w 2 ⋅ x 2 + w 3 ⋅ x 3 + w 0 y = w_1 \cdot x + w_2 \cdot x^2 + w_3 \cdot x^3 + w_0 y=w1⋅x+w2⋅x2+w3⋅x3+w0
等价于:
y = w 1 ⋅ z 1 + w 2 ⋅ z 2 + w 3 ⋅ z 3 + w 0 y = w_1 \cdot z_1 + w_2 \cdot z_2 + w_3 \cdot z_3 + w_0 y=w1⋅z1+w2⋅z2+w3⋅z3+w0
其中 z 1 = x , z 2 = x 2 , z 3 = x 3 z_1=x, z_2=x^2, z_3=x^3 z1=x,z2=x2,z3=x3
这就是特征扩展的思想:通过创造新特征,把非线性问题转化为线性问题!
📌 多项式特征扩展 ⭐⭐
🔮 直觉理解
多项式特征扩展就像"变魔术",把一个特征 x x x变成多个特征 x , x 2 , x 3 , ⋯ x, x^2, x^3, \cdots x,x2,x3,⋯。原本只有一个维度的数据,现在有了多个维度,模型就能学习更复杂的模式。
🛠️ PolynomialFeatures详解
from sklearn.preprocessing import PolynomialFeatures
import numpy as np
# 原始数据
X = np.array([[2], [3], [4]])
# 创建多项式特征扩展器(最高次数为3)
poly = PolynomialFeatures(degree=3, include_bias=True)
X_poly = poly.fit_transform(X)
print("原始特征:\n", X)
print("\n扩展后特征:\n", X_poly)
print("\n特征名称:", poly.get_feature_names_out())
输出结果:
原始特征:
[[2]
[3]
[4]]
扩展后特征:
[[ 1. 2. 4. 8.]
[ 1. 3. 9. 27.]
[ 1. 4. 16. 64.]]
特征名称: ['1' 'x0' 'x0^2' 'x0^3']
📊 API参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
degree | 多项式最高次数 | 2 |
include_bias | 是否包含常数项(1) | True |
interaction_only | 是否只包含交互项 | False |
⚠️ 特征爆炸问题:
对于 d d d个特征, n n n次多项式扩展后的特征数量为:
( d + n n ) = ( d + n ) ! d ! ⋅ n ! \binom{d+n}{n} = \frac{(d+n)!}{d! \cdot n!} (nd+n)=d!⋅n!(d+n)!
例如:
- 10个特征,2次多项式 → 66个特征
- 10个特征,3次多项式 → 286个特征
- 100个特征,2次多项式 → 5151个特征
💡 解决方案:
- 使用正则化(Ridge、Lasso)
- 特征选择
- 降维(PCA)
📌 多项式回归实现
🛠️ 完整代码示例
import numpy as np
import sklearn.linear_model as lm
import sklearn.metrics as sm
import matplotlib.pyplot as plt
import sklearn.pipeline as pl
import sklearn.preprocessing as sp
# 1. 读取数据
train_x, train_y = [], []
with open("poly_sample.txt", "rt") as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(",")]
train_x.append(data[:-1]) # 特征
train_y.append(data[-1]) # 标签
train_x = np.array(train_x)
train_y = np.array(train_y)
# 2. 构建多项式回归模型(使用管道)
model = pl.make_pipeline(
sp.PolynomialFeatures(degree=3), # 多项式特征扩展
lm.LinearRegression() # 线性回归
)
# 3. 训练模型
model.fit(train_x, train_y)
# 4. 预测训练集
pred_train_y = model.predict(train_x)
# 5. 评估模型
r2 = sm.r2_score(train_y, pred_train_y)
print(f"R² 分数: {r2:.4f}")
# 6. 生成测试数据(用于绘制平滑曲线)
test_x = np.linspace(train_x.min(), train_x.max(), 100).reshape(-1, 1)
pred_test_y = model.predict(test_x)
# 7. 可视化
plt.figure(figsize=(10, 6))
plt.scatter(train_x, train_y, c='dodgerblue', s=60, label='训练数据')
plt.plot(test_x, pred_test_y, c='orangered', linewidth=2, label='多项式拟合')
plt.xlabel('X')
plt.ylabel('Y')
plt.title(f'多项式回归 (degree=3, R²={r2:.4f})')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
🔧 make_pipeline详解
make_pipeline是Sklearn提供的便捷工具,用于串联多个处理步骤:
# 方式1:使用make_pipeline(推荐)
model = pl.make_pipeline(
sp.PolynomialFeatures(3),
lm.LinearRegression()
)
# 方式2:使用Pipeline(更灵活)
from sklearn.pipeline import Pipeline
model = Pipeline([
('poly', sp.PolynomialFeatures(3)),
('linear', lm.LinearRegression())
])
# 访问管道中的模型
print("系数:", model.named_steps['linear'].coef_)
print("截距:", model.named_steps['linear'].intercept_)
💎 Pipeline的优势:
- ✅ 代码简洁,避免中间变量
- ✅ 防止数据泄露(测试集不参与fit)
- ✅ 便于交叉验证和网格搜索
- ✅ 模型保存和加载更方便
📌 过拟合与欠拟合 ⭐⭐⭐
🔮 直觉理解
欠拟合:就像一个学生只记住了课本上的公式,但不会灵活运用,考试成绩很差。模型太简单,没有学到数据的规律。
过拟合:就像一个学生把所有题目和答案都死记硬背,但遇到新题就不会做了。模型太复杂,把训练数据的噪声也学进去了。
恰当拟合:学生既掌握了基本原理,又能灵活应用,新题也能做对。模型复杂度适中,泛化能力强。
📐 三种拟合状态
| 状态 | 训练集表现 | 测试集表现 | 原因 | 解决方案 |
|---|---|---|---|---|
| 欠拟合 | 差 | 差 | 模型太简单 | 增加模型复杂度 |
| 恰当拟合 | 好 | 好 | 复杂度适中 | 保持现状 |
| 过拟合 | 很好 | 差 | 模型太复杂 | 降低模型复杂度 |
🔍 如何判断?
通过R²分数判断:
| 训练集R² | 测试集R² | 判断 |
|---|---|---|
| 0.6 | 0.5 | 欠拟合 |
| 0.9 | 0.6 | 过拟合 |
| 0.9 | 0.88 | 恰当拟合 ✅ |
通过学习曲线判断:
from sklearn.model_selection import learning_curve
# 绘制学习曲线
train_sizes, train_scores, test_scores = learning_curve(
model, X, y, cv=5,
train_sizes=np.linspace(0.1, 1.0, 10)
)
plt.plot(train_sizes, train_scores.mean(axis=1), label='训练集')
plt.plot(train_sizes, test_scores.mean(axis=1), label='测试集')
plt.xlabel('训练样本数量')
plt.ylabel('分数')
plt.legend()
plt.show()
🛠️ 不同次数的多项式对比
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
# 生成数据
np.random.seed(42)
X = np.sort(np.random.rand(20, 1) * 10, axis=0)
y = np.sin(X).ravel() + np.random.randn(20) * 0.1
# 测试不同次数
degrees = [1, 3, 10, 20]
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
for idx, degree in enumerate(degrees):
ax = axes[idx // 2, idx % 2]
# 训练模型
model = make_pipeline(PolynomialFeatures(degree), LinearRegression())
model.fit(X, y)
# 预测
X_test = np.linspace(0, 10, 100).reshape(-1, 1)
y_pred = model.predict(X_test)
# 计算R²
r2 = model.score(X, y)
# 绘图
ax.scatter(X, y, color='blue', s=30, label='数据')
ax.plot(X_test, y_pred, color='red', linewidth=2, label=f'degree={degree}')
ax.set_title(f'多项式次数={degree}, R²={r2:.4f}')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
💡 如何选择多项式次数?
- 交叉验证:使用k折交叉验证选择最佳次数
- 验证曲线:绘制不同次数下的训练/验证分数
- 经验法则:
- 数据量小:使用低次多项式(2-3次)
- 数据量大:可以尝试高次多项式(4-5次)
- 避免过高次数(>10次)
from sklearn.model_selection import validation_curve
# 绘制验证曲线
param_range = np.arange(1, 11)
train_scores, test_scores = validation_curve(
make_pipeline(PolynomialFeatures(), LinearRegression()),
X, y, param_name="polynomialfeatures__degree",
param_range=param_range, cv=5
)
plt.plot(param_range, train_scores.mean(axis=1), label='训练集')
plt.plot(param_range, test_scores.mean(axis=1), label='验证集')
plt.xlabel('多项式次数')
plt.ylabel('R² 分数')
plt.legend()
plt.title('验证曲线')
plt.show()
🤔 思考题
- 为什么多项式回归本质上还是线性回归?
- 多项式次数越高,模型一定越好吗?
- 如何在不增加多项式次数的情况下提高模型性能?
💪 动手练习
练习1:拟合正弦曲线
生成正弦曲线数据,使用不同次数的多项式拟合:
import numpy as np
# 生成数据
X = np.linspace(0, 2*np.pi, 50).reshape(-1, 1)
y = np.sin(X).ravel() + np.random.randn(50) * 0.1
# 尝试degree=1, 3, 5, 7, 9
# 你的代码...
练习2:房价预测(非线性)
假设房价与面积的关系不是线性的,而是二次关系:
# 生成数据:price = 0.5 * area^2 + 10 * area + 50
area = np.array([50, 60, 70, 80, 90, 100]).reshape(-1, 1)
price = 0.5 * area**2 + 10 * area + 50 + np.random.randn(6, 1) * 10
# 使用多项式回归拟合
# 你的代码...
🔗 延伸阅读
- 样条回归(Spline Regression)
- 局部加权回归(LOWESS)
- 核方法与非线性回归
四、正则化回归
💡 核心要点:正则化是防止过拟合的利器。通过在损失函数中添加惩罚项,限制模型参数的大小,从而提高模型的泛化能力。
📌 什么是正则化?

🔮 直觉理解
正则化就像给模型加上"约束"。想象你在训练一个学生,如果不加约束,他可能会死记硬背所有题目(过拟合)。正则化就是告诉他:“不要太追求完美记住每道题,要学会抓住重点,理解原理。”
📐 数学定义
标准线性回归损失函数:
Loss = 1 2 n ∑ i = 1 n ( y i − y ^ i ) 2 \text{Loss} = \frac{1}{2n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 Loss=2n1i=1∑n(yi−y^i)2
正则化后的损失函数:
Loss reg = 1 2 n ∑ i = 1 n ( y i − y ^ i ) 2 + λ ⋅ Penalty \text{Loss}_{\text{reg}} = \frac{1}{2n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + \lambda \cdot \text{Penalty} Lossreg=2n1i=1∑n(yi−y^i)2+λ⋅Penalty
其中:
- λ \lambda λ : 正则化强度(超参数)
- Penalty \text{Penalty} Penalty : 惩罚项(L1或L2范数)
🤔 为什么需要正则化?
过拟合的根本原因:模型参数值过大,对训练数据过度敏感
正则化的作用:
- ✅ 防止过拟合:限制参数大小,提高泛化能力
- ✅ 特征选择:L1正则化可以产生稀疏解
- ✅ 提高稳定性:减少模型对噪声的敏感度
📌 L1正则化与L2正则化 ⭐⭐⭐
📐 数学定义
L1范数(曼哈顿距离):
∣ ∣ w ∣ ∣ 1 = ∑ i = 1 n ∣ w i ∣ ||w||_1 = \sum_{i=1}^{n}|w_i| ∣∣w∣∣1=i=1∑n∣wi∣
L2范数(欧式距离):
∣ ∣ w ∣ ∣ 2 = ∑ i = 1 n w i 2 ||w||_2 = \sqrt{\sum_{i=1}^{n}w_i^2} ∣∣w∣∣2=i=1∑nwi2
🔍 L1 vs L2 对比
| 对比项 | L1正则化 | L2正则化 |
|---|---|---|
| 公式 | λ ∑ ∣ w i ∣ \lambda\sum|w_i| λ∑∣wi∣ | λ ∑ w i 2 \lambda\sum w_i^2 λ∑wi2 |
| 几何意义 | 菱形约束 | 圆形约束 |
| 参数特点 | 产生稀疏解(部分为0) | 参数均匀收缩 |
| 特征选择 | ✅ 可以 | ❌ 不可以 |
| 计算复杂度 | 较高(不可导) | 较低(可导) |
| 适用场景 | 特征很多,需要选择 | 特征相关性强 |
🔮 直觉理解
L1正则化:像一个严格的老师,会直接"开除"不重要的特征(权重变为0)。适合特征选择。
L2正则化:像一个温和的老师,会让所有特征都"低调一点"(权重变小但不为0)。适合所有特征都有用的情况。
📌 岭回归(Ridge Regression)⭐⭐
📐 数学定义
损失函数:
Loss Ridge = 1 2 n ∑ i = 1 n ( y i − y ^ i ) 2 + α ∑ j = 1 p w j 2 \text{Loss}_{\text{Ridge}} = \frac{1}{2n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + \alpha\sum_{j=1}^{p}w_j^2 LossRidge=2n1i=1∑n(yi−y^i)2+αj=1∑pwj2
其中:
- α \alpha α : 正则化强度( α ≥ 0 \alpha \geq 0 α≥0)
- α = 0 \alpha = 0 α=0 : 退化为标准线性回归
- α → ∞ \alpha \to \infty α→∞ : 所有权重趋向于0
🛠️ 代码实现
import numpy as np
import sklearn.linear_model as lm
import matplotlib.pyplot as plt
# 读取数据(包含异常值)
x, y = [], []
with open("abnormal.txt", "rt") as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(",")]
x.append(data[:-1])
y.append(data[-1])
x = np.array(x)
y = np.array(y)
# 1. 标准线性回归
model_lr = lm.LinearRegression()
model_lr.fit(x, y)
pred_y_lr = model_lr.predict(x)
# 2. 岭回归
model_ridge = lm.Ridge(
alpha=200, # 正则化强度
max_iter=1000 # 最大迭代次数
)
model_ridge.fit(x, y)
pred_y_ridge = model_ridge.predict(x)
# 可视化对比
sorted_idx = x.T[0].argsort()
plt.figure(figsize=(10, 6))
plt.scatter(x, y, c='dodgerblue', s=60, label='数据点')
plt.plot(x[sorted_idx], pred_y_lr[sorted_idx],
c='red', linewidth=2, label='线性回归')
plt.plot(x[sorted_idx], pred_y_ridge[sorted_idx],
c='green', linewidth=2, label='岭回归')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('岭回归 vs 线性回归')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
# 打印系数对比
print("线性回归系数:", model_lr.coef_)
print("岭回归系数:", model_ridge.coef_)
📊 Ridge API参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
alpha | 正则化强度 | 1.0 |
max_iter | 最大迭代次数 | None |
solver | 求解器 | ‘auto’ |
random_state | 随机种子 | None |
💡 alpha选择建议:
- 从小到大尝试:0.01, 0.1, 1, 10, 100, 1000
- 使用交叉验证选择最佳值
- 使用
RidgeCV自动选择
from sklearn.linear_model import RidgeCV
# 自动选择最佳alpha
model = RidgeCV(alphas=[0.1, 1, 10, 100, 1000], cv=5)
model.fit(x, y)
print("最佳alpha:", model.alpha_)
📌 Lasso回归(Lasso Regression)⭐⭐
📐 数学定义
损失函数:
Loss Lasso = 1 2 n ∑ i = 1 n ( y i − y ^ i ) 2 + α ∑ j = 1 p ∣ w j ∣ \text{Loss}_{\text{Lasso}} = \frac{1}{2n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + \alpha\sum_{j=1}^{p}|w_j| LossLasso=2n1i=1∑n(yi−y^i)2+αj=1∑p∣wj∣
Lasso的特点:
- ✅ 可以产生稀疏解(部分权重为0)
- ✅ 自动进行特征选择
- ✅ 适合高维数据(特征数 >> 样本数)
🛠️ 代码实现
import numpy as np
import sklearn.linear_model as lm
import matplotlib.pyplot as plt
# 使用相同的数据
# ... (数据加载代码同上)
# 1. 标准线性回归
model_lr = lm.LinearRegression()
model_lr.fit(x, y)
pred_y_lr = model_lr.predict(x)
# 2. 岭回归
model_ridge = lm.Ridge(alpha=200, max_iter=1000)
model_ridge.fit(x, y)
pred_y_ridge = model_ridge.predict(x)
# 3. Lasso回归
model_lasso = lm.Lasso(
alpha=0.5, # 正则化强度
max_iter=1000 # 最大迭代次数
)
model_lasso.fit(x, y)
pred_y_lasso = model_lasso.predict(x)
# 可视化对比
sorted_idx = x.T[0].argsort()
plt.figure(figsize=(12, 6))
plt.scatter(x, y, c='dodgerblue', s=60, label='数据点')
plt.plot(x[sorted_idx], pred_y_lr[sorted_idx],
c='red', linewidth=2, label='线性回归')
plt.plot(x[sorted_idx], pred_y_ridge[sorted_idx],
c='green', linewidth=2, label='岭回归')
plt.plot(x[sorted_idx], pred_y_lasso[sorted_idx],
c='blue', linewidth=2, label='Lasso回归')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('三种回归方法对比')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
# 打印系数对比
print("线性回归系数:", model_lr.coef_)
print("岭回归系数:", model_ridge.coef_)
print("Lasso回归系数:", model_lasso.coef_)
print("\nLasso非零系数数量:", np.sum(model_lasso.coef_ != 0))
📊 Lasso API参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
alpha | 正则化强度 | 1.0 |
max_iter | 最大迭代次数 | 1000 |
selection | 特征选择策略 | ‘cyclic’ |
random_state | 随机种子 | None |
🔍 Ridge vs Lasso 实战对比
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge, Lasso
# 生成数据:只有3个特征真正有用
np.random.seed(42)
n_samples, n_features = 100, 20
X = np.random.randn(n_samples, n_features)
# 只有前3个特征有用
coef = np.zeros(n_features)
coef[:3] = [1.5, -2.0, 1.0]
y = X @ coef + np.random.randn(n_samples) * 0.1
# 训练模型
ridge = Ridge(alpha=1.0).fit(X, y)
lasso = Lasso(alpha=0.1).fit(X, y)
# 可视化系数
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].bar(range(n_features), ridge.coef_)
axes[0].set_title('岭回归系数')
axes[0].set_xlabel('特征索引')
axes[0].set_ylabel('系数值')
axes[0].axhline(y=0, color='r', linestyle='--')
axes[1].bar(range(n_features), lasso.coef_)
axes[1].set_title('Lasso回归系数')
axes[1].set_xlabel('特征索引')
axes[1].set_ylabel('系数值')
axes[1].axhline(y=0, color='r', linestyle='--')
plt.tight_layout()
plt.show()
print(f"岭回归非零系数: {np.sum(ridge.coef_ != 0)}")
print(f"Lasso非零系数: {np.sum(lasso.coef_ != 0)}")
📌 ElasticNet回归 ⭐⭐⭐
🔮 直觉理解
ElasticNet是Ridge和Lasso的结合体,同时使用L1和L2正则化。就像一个既严格又温和的老师,既会"开除"不重要的特征,又会让重要特征保持低调。
📐 数学定义
损失函数:
Loss ElasticNet = 1 2 n ∑ i = 1 n ( y i − y ^ i ) 2 + α ρ ∑ j = 1 p ∣ w j ∣ + α ( 1 − ρ ) 2 ∑ j = 1 p w j 2 \text{Loss}_{\text{ElasticNet}} = \frac{1}{2n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + \alpha\rho\sum_{j=1}^{p}|w_j| + \frac{\alpha(1-\rho)}{2}\sum_{j=1}^{p}w_j^2 LossElasticNet=2n1i=1∑n(yi−y^i)2+αρj=1∑p∣wj∣+2α(1−ρ)j=1∑pwj2
其中:
- α \alpha α : 正则化强度
-
ρ
\rho
ρ : L1比例(0到1之间)
- ρ = 0 \rho = 0 ρ=0 : 纯L2(Ridge)
- ρ = 1 \rho = 1 ρ=1 : 纯L1(Lasso)
- 0 < ρ < 1 0 < \rho < 1 0<ρ<1 : L1和L2混合
🛠️ 代码实现
from sklearn.linear_model import ElasticNet
model = ElasticNet(
alpha=1.0, # 正则化强度
l1_ratio=0.5, # L1比例(对应ρ)
max_iter=1000
)
model.fit(X, y)
🤔 思考题
- 为什么L1正则化可以产生稀疏解,而L2不能?
- 在什么情况下应该使用Ridge而不是Lasso?
- 如何选择合适的正则化强度α?
💪 动手练习
练习1:正则化强度对比
使用不同的α值训练岭回归模型,观察系数变化:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge
# 生成数据
X = np.random.randn(100, 10)
y = X @ np.random.randn(10) + np.random.randn(100) * 0.1
# 尝试不同的alpha值
alphas = [0.01, 0.1, 1, 10, 100, 1000]
# 你的代码...
练习2:特征选择
使用Lasso进行特征选择:
# 生成数据:100个特征,只有5个有用
n_samples, n_features = 200, 100
X = np.random.randn(n_samples, n_features)
coef = np.zeros(n_features)
coef[:5] = np.random.randn(5)
y = X @ coef + np.random.randn(n_samples) * 0.1
# 使用Lasso选择特征
# 你的代码...
🔗 延伸阅读
- 正则化路径(Regularization Path)
- 交叉验证选择超参数
- Group Lasso和Sparse Group Lasso
五、模型评估与保存
💡 核心要点:训练好的模型需要保存下来,以便后续使用。同时,要全面评估模型性能,确保模型在实际应用中表现良好。
📌 模型保存与加载 ⭐⭐
🔮 直觉理解
模型保存就像把训练好的"大脑"存储到硬盘上,下次使用时直接加载,不需要重新训练。这样可以节省大量时间和计算资源。
🛠️ 使用pickle保存模型
import pickle
import numpy as np
import sklearn.linear_model as lm
# 1. 训练模型
X = np.array([[0.5], [0.6], [0.8], [1.1], [1.4]])
y = np.array([5.0, 5.5, 6.0, 6.8, 7.0])
model = lm.LinearRegression()
model.fit(X, y)
print("训练完成")
# 2. 保存模型
with open('linear_model.pkl', 'wb') as f:
pickle.dump(model, f)
print("模型已保存到 linear_model.pkl")
# 3. 加载模型
with open('linear_model.pkl', 'rb') as f:
loaded_model = pickle.load(f)
print("模型加载成功")
# 4. 使用加载的模型预测
pred_y = loaded_model.predict(X)
print("预测结果:", pred_y)
🛠️ 使用joblib保存模型(推荐)✅
from joblib import dump, load
import sklearn.linear_model as lm
# 训练模型
model = lm.LinearRegression()
model.fit(X, y)
# 保存模型(joblib对大型numpy数组更高效)
dump(model, 'linear_model.joblib')
print("模型已保存")
# 加载模型
loaded_model = load('linear_model.joblib')
print("模型加载成功")
📊 pickle vs joblib对比
| 对比项 | pickle | joblib |
|---|---|---|
| 速度 | 较慢 | 快(特别是大数组) |
| 压缩 | 不支持 | 支持 |
| 兼容性 | Python标准库 | 需要安装 |
| 推荐场景 | 小模型 | 大模型、生产环境 |
⚠️ 注意事项:
- 保存前确保模型已训练完成
- 加载模型时要确保sklearn版本一致
- 不要保存训练数据,只保存模型参数
📌 交叉验证(Cross-Validation)⭐⭐⭐
🔮 直觉理解
交叉验证就像多次考试取平均分。如果只考一次,可能因为运气好或坏导致分数不准确。多考几次取平均,才能真实反映水平。
📐 K折交叉验证
原理:
- 将数据分成K份
- 每次用K-1份训练,1份测试
- 重复K次,每份都做一次测试集
- 计算K次结果的平均值
🛠️ 代码实现
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression
import numpy as np
# 生成数据
X = np.random.randn(100, 5)
y = X @ np.random.randn(5) + np.random.randn(100) * 0.1
# 创建模型
model = LinearRegression()
# 5折交叉验证
scores = cross_val_score(model, X, y, cv=5, scoring='r2')
print("各折R²分数:", scores)
print("平均R²分数:", scores.mean())
print("标准差:", scores.std())
输出结果:
各折R²分数: [0.9876 0.9823 0.9901 0.9845 0.9889]
平均R²分数: 0.9867
标准差: 0.0029
🔧 交叉验证策略
from sklearn.model_selection import (
KFold, StratifiedKFold, LeaveOneOut, ShuffleSplit
)
# 1. K折交叉验证
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
# 2. 分层K折(用于分类,保持类别比例)
skfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 3. 留一法(样本量小时使用)
loo = LeaveOneOut()
# 4. 随机划分
shuffle_split = ShuffleSplit(n_splits=5, test_size=0.2, random_state=42)
# 使用自定义策略
scores = cross_val_score(model, X, y, cv=kfold, scoring='r2')
📌 网格搜索(Grid Search)⭐⭐⭐
🔮 直觉理解
网格搜索就像在一个参数空间中"地毯式搜索",尝试所有可能的参数组合,找到最佳配置。
🛠️ 代码实现
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import Ridge
import numpy as np
# 生成数据
X = np.random.randn(100, 10)
y = X @ np.random.randn(10) + np.random.randn(100) * 0.1
# 定义参数网格
param_grid = {
'alpha': [0.01, 0.1, 1, 10, 100, 1000],
'solver': ['auto', 'svd', 'cholesky']
}
# 创建网格搜索对象
grid_search = GridSearchCV(
Ridge(),
param_grid,
cv=5, # 5折交叉验证
scoring='r2', # 评分标准
n_jobs=-1, # 使用所有CPU核心
verbose=1 # 显示进度
)
# 执行搜索
grid_search.fit(X, y)
# 查看结果
print("最佳参数:", grid_search.best_params_)
print("最佳分数:", grid_search.best_score_)
print("最佳模型:", grid_search.best_estimator_)
# 查看所有结果
results = pd.DataFrame(grid_search.cv_results_)
print(results[['params', 'mean_test_score', 'std_test_score']])
🔧 随机搜索(更高效)
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform, randint
# 定义参数分布
param_distributions = {
'alpha': uniform(0.01, 1000), # 连续分布
'max_iter': randint(100, 2000) # 离散分布
}
# 随机搜索
random_search = RandomizedSearchCV(
Ridge(),
param_distributions,
n_iter=50, # 尝试50种组合
cv=5,
scoring='r2',
n_jobs=-1,
random_state=42
)
random_search.fit(X, y)
print("最佳参数:", random_search.best_params_)
🤔 思考题
- 为什么要使用交叉验证而不是简单的训练集/测试集划分?
- 网格搜索和随机搜索各有什么优缺点?
- 如何选择合适的交叉验证折数K?
💪 动手练习
练习1:完整的模型训练流程
# 1. 加载数据
# 2. 数据预处理(标准化)
# 3. 划分训练集和测试集
# 4. 使用网格搜索找最佳参数
# 5. 在测试集上评估
# 6. 保存最佳模型
# 你的代码...
练习2:模型对比
对比线性回归、Ridge、Lasso、ElasticNet在同一数据集上的表现:
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.model_selection import cross_val_score
models = {
'Linear': LinearRegression(),
'Ridge': Ridge(alpha=1.0),
'Lasso': Lasso(alpha=0.1),
'ElasticNet': ElasticNet(alpha=0.1, l1_ratio=0.5)
}
# 你的代码...
📚 知识点速查表
⭐ 基础必学(入门必须掌握)
| 知识点 | 核心内容 | 应用场景 |
|---|---|---|
| 标准化 | ( x − μ ) / σ (x-\mu)/\sigma (x−μ)/σ | 梯度下降、KNN、SVM |
| 归一化 | ( x − m i n ) / ( m a x − m i n ) (x-min)/(max-min) (x−min)/(max−min) | 神经网络、图像处理 |
| 线性回归 | y = w x + b y=wx+b y=wx+b | 连续值预测 |
| 损失函数 | MSE、MAE | 模型优化 |
| 梯度下降 | 参数更新 | 模型训练 |
| R²分数 | 模型评估 | 性能度量 |
⭐⭐ 进阶提升(熟练应用需要)
| 知识点 | 核心内容 | 应用场景 |
|---|---|---|
| 独热编码 | 类别→向量 | 类别特征处理 |
| 多项式回归 | 特征扩展 | 非线性拟合 |
| 过拟合/欠拟合 | 模型复杂度 | 模型调优 |
| 交叉验证 | K折验证 | 模型评估 |
| 模型保存 | pickle/joblib | 模型部署 |
⭐⭐⭐ 高级技巧(深入研究方向)
| 知识点 | 核心内容 | 应用场景 |
|---|---|---|
| 岭回归 | L2正则化 | 防止过拟合 |
| Lasso回归 | L1正则化 | 特征选择 |
| ElasticNet | L1+L2 | 高维数据 |
| 网格搜索 | 超参数优化 | 模型调优 |
| 学习曲线 | 诊断工具 | 问题定位 |
🗺️ 知识点思维导图
机器学习基础(回归篇)
│
├── 1. 数据预处理 ⭐
│ ├── 标准化(Standardization)
│ │ ├── 原理:z = (x - μ) / σ
│ │ ├── API:sklearn.preprocessing.StandardScaler
│ │ ├── 方法:fit(), transform(), fit_transform()
│ │ └── 场景:梯度下降、KNN、SVM、PCA
│ │
│ ├── 归一化(Min-Max Scaling)
│ │ ├── 原理:x' = (x - min) / (max - min)
│ │ ├── API:sklearn.preprocessing.MinMaxScaler
│ │ ├── 参数:feature_range=(0, 1)
│ │ └── 场景:神经网络、图像处理
│ │
│ ├── L1/L2归一化
│ │ ├── L1:x' = x / sum(|x|)
│ │ ├── L2:x' = x / sqrt(sum(x²))
│ │ ├── API:sklearn.preprocessing.normalize
│ │ └── 场景:文本分类、余弦相似度
│ │
│ ├── 二值化(Binarization)
│ │ ├── 原理:x > threshold → 1, else → 0
│ │ ├── API:sklearn.preprocessing.Binarizer
│ │ └── 场景:图像二值化、特征离散化
│ │
│ ├── 独热编码(One-Hot Encoding)
│ │ ├── 原理:类别 → 多维0/1向量
│ │ ├── API:sklearn.preprocessing.OneHotEncoder
│ │ ├── 参数:sparse, dtype, categories
│ │ └── 场景:类别特征、决策树、神经网络
│ │
│ └── 标签编码(Label Encoding)
│ ├── 原理:字符串 → 整数
│ ├── API:sklearn.preprocessing.LabelEncoder
│ └── 场景:目标变量、有序类别
│
├── 2. 线性回归 ⭐⭐
│ ├── 基本原理
│ │ ├── 模型:y = w₁x₁ + w₂x₂ + ... + w₀
│ │ ├── 向量形式:y = wᵀx + b
│ │ └── 几何意义:直线/平面/超平面
│ │
│ ├── 损失函数
│ │ ├── MSE:(1/2n)Σ(y - ŷ)²
│ │ ├── 几何意义:欧式距离
│ │ └── 优化目标:最小化损失
│ │
│ ├── 梯度下降法
│ │ ├── 原理:沿负梯度方向更新参数
│ │ ├── 更新公式:w = w - η∇L
│ │ ├── 学习率:η ∈ {0.1, 0.01, 0.001}
│ │ ├── 梯度计算:∂L/∂w₀, ∂L/∂w₁
│ │ └── 终止条件:梯度≈0 或 达到最大迭代次数
│ │
│ ├── sklearn实现
│ │ ├── API:sklearn.linear_model.LinearRegression
│ │ ├── 训练:model.fit(X, y)
│ │ ├── 预测:model.predict(X)
│ │ ├── 系数:model.coef_(权重w)
│ │ └── 截距:model.intercept_(偏置b)
│ │
│ └── 模型评估
│ ├── R²分数:1 - SSE/SST(越接近1越好)
│ ├── MSE:mean_squared_error()
│ ├── RMSE:sqrt(MSE)(与目标同量纲)
│ └── MAE:mean_absolute_error()(对异常值鲁棒)
│
├── 3. 多项式回归 ⭐⭐
│ ├── 适用场景:非线性数据拟合
│ ├── 核心思想:特征扩展 + 线性回归
│ │
│ ├── 数学模型
│ │ ├── 一元n次:y = w₀ + w₁x + w₂x² + ... + wₙxⁿ
│ │ ├── 本质:线性回归(对扩展特征)
│ │ └── 特征:x → [1, x, x², x³, ...]
│ │
│ ├── 实现方式
│ │ ├── PolynomialFeatures:多项式特征扩展
│ │ │ ├── 参数:degree(最高次数)
│ │ │ ├── 参数:include_bias(是否包含常数项)
│ │ │ └── 方法:fit_transform()
│ │ │
│ │ ├── make_pipeline:模型管道串联
│ │ │ ├── 步骤1:PolynomialFeatures
│ │ │ ├── 步骤2:LinearRegression
│ │ │ └── 优势:防止数据泄露、便于调参
│ │ │
│ │ └── 超参数:degree(多项式最高次数)
│ │ ├── 选择方法:交叉验证、验证曲线
│ │ └── 经验值:2-5次(避免过高)
│ │
│ ├── 过拟合与欠拟合
│ │ ├── 欠拟合:模型太简单,训练/测试都差
│ │ │ └── 解决:增加模型复杂度(提高degree)
│ │ │
│ │ ├── 过拟合:模型太复杂,训练好测试差
│ │ │ └── 解决:降低复杂度、正则化、增加数据
│ │ │
│ │ └── 恰当拟合:训练/测试都好
│ │ └── 判断:R²训练≈R²测试,且都较高
│ │
│ └── 注意事项
│ ├── 特征爆炸:高次项导致特征数激增
│ ├── 计算复杂度:随次数指数增长
│ └── 过拟合风险:次数过高易过拟合
│
├── 4. 正则化回归 ⭐⭐⭐
│ ├── 正则化原理
│ │ ├── 目的:防止过拟合,提高泛化能力
│ │ ├── 方法:在损失函数中添加惩罚项
│ │ ├── 效果:限制参数大小,降低模型复杂度
│ │ └── 公式:Loss = MSE + λ·Penalty
│ │
│ ├── L1/L2范数
│ │ ├── L1范数:||w||₁ = Σ|wᵢ|(曼哈顿距离)
│ │ ├── L2范数:||w||₂ = √(Σwᵢ²)(欧式距离)
│ │ └── 对比:L1产生稀疏解,L2均匀收缩
│ │
│ ├── 岭回归(Ridge)
│ │ ├── 正则项:L2 = α·Σwᵢ²
│ │ ├── 特点:系数收缩但不为零
│ │ ├── API:sklearn.linear_model.Ridge
│ │ ├── 参数:alpha(正则化强度)
│ │ ├── 选择:RidgeCV自动选择alpha
│ │ └── 场景:特征相关性强、防止过拟合
│ │
│ ├── Lasso回归
│ │ ├── 正则项:L1 = α·Σ|wᵢ|
│ │ ├── 特点:产生稀疏解(特征选择)
│ │ ├── API:sklearn.linear_model.Lasso
│ │ ├── 参数:alpha(正则化强度)
│ │ ├── 选择:LassoCV自动选择alpha
│ │ └── 场景:高维数据、特征选择
│ │
│ ├── ElasticNet回归
│ │ ├── 正则项:L1 + L2混合
│ │ ├── 公式:α·ρ·L1 + α·(1-ρ)·L2
│ │ ├── API:sklearn.linear_model.ElasticNet
│ │ ├── 参数:alpha, l1_ratio(L1比例)
│ │ └── 场景:结合Ridge和Lasso优点
│ │
│ └── 正则化对比
│ ├── Ridge:所有特征保留,权重变小
│ ├── Lasso:部分特征权重为0,自动选择
│ └── ElasticNet:兼顾两者,更稳定
│
└── 5. 模型评估与保存 ⭐
├── 评估指标
│ ├── R²决定系数:1 - SSE/SST
│ │ ├── 范围:(-∞, 1]
│ │ ├── 含义:模型解释的方差比例
│ │ └── 判断:>0.9优秀,0.7-0.9良好
│ │
│ ├── MSE均方误差:(1/n)Σ(y-ŷ)²
│ │ ├── 特点:对大误差敏感
│ │ └── 用途:模型训练优化
│ │
│ ├── RMSE均方根误差:√MSE
│ │ ├── 特点:与原数据同量纲
│ │ └── 用途:业务评估
│ │
│ └── MAE平均绝对误差:(1/n)Σ|y-ŷ|
│ ├── 特点:对异常值鲁棒
│ └── 用途:有异常值时
│
├── 交叉验证
│ ├── K折交叉验证(KFold)
│ │ ├── 原理:数据分K份,轮流做测试集
│ │ ├── API:cross_val_score()
│ │ ├── 参数:cv(折数,常用5或10)
│ │ └── 优势:充分利用数据,评估更准确
│ │
│ ├── 分层K折(StratifiedKFold)
│ │ └── 用途:分类问题,保持类别比例
│ │
│ ├── 留一法(LeaveOneOut)
│ │ └── 用途:样本量很小时
│ │
│ └── 随机划分(ShuffleSplit)
│ └── 用途:快速评估
│
├── 超参数优化
│ ├── 网格搜索(GridSearchCV)
│ │ ├── 原理:遍历所有参数组合
│ │ ├── 优势:找到全局最优
│ │ └── 劣势:计算量大
│ │
│ └── 随机搜索(RandomizedSearchCV)
│ ├── 原理:随机采样参数组合
│ ├── 优势:更高效
│ └── 适用:参数空间大时
│
└── 模型保存
├── pickle模块
│ ├── 保存:pickle.dump(model, file)
│ ├── 加载:pickle.load(file)
│ └── 适用:小模型
│
└── joblib模块(推荐)
├── 保存:dump(model, 'file.joblib')
├── 加载:load('file.joblib')
├── 优势:更快、支持压缩
└── 适用:大模型、生产环境
📖 参考资料与延伸阅读
📚 推荐书籍
- 《机器学习》- 周志华(西瓜书)
- 《统计学习方法》- 李航
- 《Python机器学习基础教程》
- 《Hands-On Machine Learning》
🌐 在线资源
- Scikit-learn官方文档
- Kaggle机器学习教程
- Coursera机器学习课程(Andrew Ng)
- 机器学习中文社区
🎯 总结
恭喜你完成了机器学习基础(回归篇)的学习!让我们回顾一下核心要点:
🔑 关键收获
-
数据预处理是基础
- 标准化和归一化是必备技能
- 独热编码处理类别特征
- 数据质量决定模型上限
-
线性回归是起点
- 理解损失函数和梯度下降
- 掌握模型训练和评估
- 为学习复杂模型打基础
-
多项式回归扩展能力
- 通过特征扩展处理非线性
- 警惕过拟合和欠拟合
- 合理选择模型复杂度
-
正则化提升泛化
- Ridge和Lasso各有优势
- 正则化是防止过拟合的利器
- 超参数优化很重要
-
评估和部署是关键
- 交叉验证评估更准确
- 模型保存便于部署
- 持续监控和优化
🚀 下一步学习方向
- 📈 分类算法:逻辑回归、决策树、随机森林
- 🧠 神经网络:深度学习基础
- 📊 无监督学习:聚类、降维
- 🎯 集成学习:Bagging、Boosting
- 🔧 特征工程:特征选择、特征构造
🌟 最后的话:机器学习是一个需要持续学习的领域。保持好奇心,多动手实践,你一定能成为优秀的机器学习工程师!加油!
本文档基于sklearn 1.0+版本编写,部分API可能在不同版本中有所差异。
更多推荐
所有评论(0)