数据预处理与回归模型实战

💡 写在前面:机器学习是人工智能的核心技术之一,它让计算机能够从数据中学习规律,做出预测和决策。本文档将带你从零开始,深入理解机器学习的基础知识,特别是回归算法的原理与实践。


📚 目录

  1. 数据预处理
  2. 线性回归
  3. 多项式回归
  4. 正则化回归
  5. 模型评估与保存

一、数据预处理

💡 核心要点:数据预处理是机器学习的第一步,也是最重要的一步。“垃圾进,垃圾出”——只有高质量的数据才能训练出好的模型。

机器学习三大范式

📌 为什么需要数据预处理?

在现实世界中,原始数据往往存在以下问题:

  • 量纲不统一:身高用厘米(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

🤔 为什么需要标准化?

  1. 消除量纲影响:身高(cm)和体重(kg)数值范围差异大,不标准化会导致大数值特征主导模型
  2. 加速梯度下降:标准化后损失函数等高线更接近圆形,优化更高效
  3. 算法要求: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=xmaxxminxxmin

其中:

  • x x x : 原始值
  • x m i n x_{min} xmin : 该特征的最小值
  • x m a x x_{max} xmax : 该特征的最大值
  • x ′ x' x : 归一化后的值(范围[0, 1])

🤔 为什么需要归一化?

  1. 统一数值范围:将所有特征缩放到相同范围,便于比较
  2. 加速收敛:神经网络等模型在[0,1]范围内训练更快
  3. 避免数值溢出:防止大数值导致的计算问题

🛠️ 代码实现

方法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) (xmin)/(maxmin)
结果范围无固定范围[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=jxjxi

L2归一化(欧式距离):

x i ′ = x i ∑ j x j 2 x'_i = \frac{x_i}{\sqrt{\sum_{j}x_j^2}} xi=jxj2 xi

🛠️ 代码实现

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 xthreshold

🛠️ 代码实现

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。

🤔 为什么需要独热编码?

  1. 消除类别大小关系:避免模型误认为类别之间有大小关系
  2. 提高模型性能:让模型能够正确理解类别特征
  3. 算法要求:大多数机器学习算法只能处理数值型数据

🛠️ 代码实现

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)的编码
  • 有序类别特征(如:低、中、高)

对于无序类别特征,应使用独热编码!


🤔 思考题

  1. 为什么标准化后的数据均值为0,标准差为1?
  2. 在什么情况下应该使用归一化而不是标准化?
  3. 独热编码会带来什么问题?如何解决?

💪 动手练习

创建一个包含身高(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=1n(yiy^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:为了求导方便

🤔 为什么使用平方?

  1. 放大误差:平方让大误差的惩罚更重
  2. 消除正负:避免正负误差相互抵消
  3. 可导性好:便于梯度下降优化
  4. 几何意义:对应欧式距离

⚠️ 其他损失函数

  • MAE(平均绝对误差) 1 n ∑ ∣ y i − y ^ i ∣ \frac{1}{n}\sum|y_i - \hat{y}_i| n1yiy^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ηw0Loss

w 1 = w 1 − η ∂ Loss ∂ w 1 w_1 = w_1 - \eta \frac{\partial \text{Loss}}{\partial w_1} w1=w1ηw1Loss

其中:

  • η \eta η : 学习率(Learning Rate),控制每次更新的步长
  • ∂ Loss ∂ w \frac{\partial \text{Loss}}{\partial w} wLoss : 损失函数对参数的偏导数(梯度)

🧮 梯度计算

对于损失函数:

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)) w0Loss=(yy^)=(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)) w1Loss=x(yy^)=x(y(w1x+w0))

🤔 为什么需要梯度下降?

最小二乘法的问题

  1. 需要计算矩阵的逆,可能不存在
  2. 当特征数量很多时,计算量巨大
  3. 无法处理大规模数据

梯度下降的优势

  1. ✅ 不需要计算矩阵的逆
  2. ✅ 可以处理大规模数据
  3. ✅ 适用于各种复杂模型

🔧 学习率的选择

学习率 η \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(yiyˉ)2(yiy^i)2=1SSTSSE

其中:

  • 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=1n(yiy^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=1n(yiy^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=1nyiy^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

🔍 指标对比

指标优点缺点适用场景
归一化,易比较不反映误差大小模型比较
MSE可导,便于优化量纲不直观模型训练
RMSE与原数据同量纲对异常值敏感业务评估
MAE鲁棒,直观不可导有异常值时

🤔 思考题

  1. 为什么梯度下降要沿着梯度的反方向移动?
  2. 学习率设置为0.1和0.001,训练过程有什么区别?
  3. R²分数为负数意味着什么?

💪 动手练习

练习1:预测房价

已知某城市房价与面积的关系数据:

面积(m²)价格(万元)
50150
80240
100300
120360
150450

要求:

  1. 使用线性回归拟合数据
  2. 预测200m²房屋的价格
  3. 计算模型的R²分数
  4. 可视化拟合结果
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=0nwixi

🤔 多项式回归 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,w1n+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=w1x+w2x2+w3x3+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=w1z1+w2z2+w3z3+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的优势

  1. ✅ 代码简洁,避免中间变量
  2. ✅ 防止数据泄露(测试集不参与fit)
  3. ✅ 便于交叉验证和网格搜索
  4. ✅ 模型保存和加载更方便

📌 过拟合与欠拟合 ⭐⭐⭐

🔮 直觉理解

欠拟合:就像一个学生只记住了课本上的公式,但不会灵活运用,考试成绩很差。模型太简单,没有学到数据的规律。

过拟合:就像一个学生把所有题目和答案都死记硬背,但遇到新题就不会做了。模型太复杂,把训练数据的噪声也学进去了。

恰当拟合:学生既掌握了基本原理,又能灵活应用,新题也能做对。模型复杂度适中,泛化能力强。

📐 三种拟合状态

状态训练集表现测试集表现原因解决方案
欠拟合模型太简单增加模型复杂度
恰当拟合复杂度适中保持现状
过拟合很好模型太复杂降低模型复杂度

🔍 如何判断?

通过R²分数判断

训练集R²测试集R²判断
0.60.5欠拟合
0.90.6过拟合
0.90.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()

💡 如何选择多项式次数?

  1. 交叉验证:使用k折交叉验证选择最佳次数
  2. 验证曲线:绘制不同次数下的训练/验证分数
  3. 经验法则
    • 数据量小:使用低次多项式(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. 为什么多项式回归本质上还是线性回归?
  2. 多项式次数越高,模型一定越好吗?
  3. 如何在不增加多项式次数的情况下提高模型性能?

💪 动手练习

练习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=1n(yiy^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=1n(yiy^i)2+λPenalty

其中:

  • λ \lambda λ : 正则化强度(超参数)
  • Penalty \text{Penalty} Penalty : 惩罚项(L1或L2范数)

🤔 为什么需要正则化?

过拟合的根本原因:模型参数值过大,对训练数据过度敏感

正则化的作用

  1. 防止过拟合:限制参数大小,提高泛化能力
  2. 特征选择:L1正则化可以产生稀疏解
  3. 提高稳定性:减少模型对噪声的敏感度

📌 L1正则化与L2正则化 ⭐⭐⭐

📐 数学定义

L1范数(曼哈顿距离)

∣ ∣ w ∣ ∣ 1 = ∑ i = 1 n ∣ w i ∣ ||w||_1 = \sum_{i=1}^{n}|w_i| ∣∣w1=i=1nwi

L2范数(欧式距离)

∣ ∣ w ∣ ∣ 2 = ∑ i = 1 n w i 2 ||w||_2 = \sqrt{\sum_{i=1}^{n}w_i^2} ∣∣w2=i=1nwi2

🔍 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=1n(yiy^i)2+αj=1pwj2

其中:

  • α \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=1n(yiy^i)2+αj=1pwj

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=1n(yiy^i)2+αρj=1pwj+2α(1ρ)j=1pwj2

其中:

  • α \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)

🤔 思考题

  1. 为什么L1正则化可以产生稀疏解,而L2不能?
  2. 在什么情况下应该使用Ridge而不是Lasso?
  3. 如何选择合适的正则化强度α?

💪 动手练习

练习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对比

对比项picklejoblib
速度较慢快(特别是大数组)
压缩不支持支持
兼容性Python标准库需要安装
推荐场景小模型大模型、生产环境

⚠️ 注意事项

  • 保存前确保模型已训练完成
  • 加载模型时要确保sklearn版本一致
  • 不要保存训练数据,只保存模型参数

📌 交叉验证(Cross-Validation)⭐⭐⭐

🔮 直觉理解

交叉验证就像多次考试取平均分。如果只考一次,可能因为运气好或坏导致分数不准确。多考几次取平均,才能真实反映水平。

📐 K折交叉验证

原理

  1. 将数据分成K份
  2. 每次用K-1份训练,1份测试
  3. 重复K次,每份都做一次测试集
  4. 计算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_)

🤔 思考题

  1. 为什么要使用交叉验证而不是简单的训练集/测试集划分?
  2. 网格搜索和随机搜索各有什么优缺点?
  3. 如何选择合适的交叉验证折数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) (xmin)/(maxmin)神经网络、图像处理
线性回归 y = w x + b y=wx+b y=wx+b连续值预测
损失函数MSE、MAE模型优化
梯度下降参数更新模型训练
R²分数模型评估性能度量

⭐⭐ 进阶提升(熟练应用需要)

知识点核心内容应用场景
独热编码类别→向量类别特征处理
多项式回归特征扩展非线性拟合
过拟合/欠拟合模型复杂度模型调优
交叉验证K折验证模型评估
模型保存pickle/joblib模型部署

⭐⭐⭐ 高级技巧(深入研究方向)

知识点核心内容应用场景
岭回归L2正则化防止过拟合
Lasso回归L1正则化特征选择
ElasticNetL1+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')
            ├── 优势:更快、支持压缩
            └── 适用:大模型、生产环境

📖 参考资料与延伸阅读

📚 推荐书籍

  1. 《机器学习》- 周志华(西瓜书)
  2. 《统计学习方法》- 李航
  3. 《Python机器学习基础教程》
  4. 《Hands-On Machine Learning》

🌐 在线资源

  1. Scikit-learn官方文档
  2. Kaggle机器学习教程
  3. Coursera机器学习课程(Andrew Ng)
  4. 机器学习中文社区

🎯 总结

恭喜你完成了机器学习基础(回归篇)的学习!让我们回顾一下核心要点:

🔑 关键收获

  1. 数据预处理是基础

    • 标准化和归一化是必备技能
    • 独热编码处理类别特征
    • 数据质量决定模型上限
  2. 线性回归是起点

    • 理解损失函数和梯度下降
    • 掌握模型训练和评估
    • 为学习复杂模型打基础
  3. 多项式回归扩展能力

    • 通过特征扩展处理非线性
    • 警惕过拟合和欠拟合
    • 合理选择模型复杂度
  4. 正则化提升泛化

    • Ridge和Lasso各有优势
    • 正则化是防止过拟合的利器
    • 超参数优化很重要
  5. 评估和部署是关键

    • 交叉验证评估更准确
    • 模型保存便于部署
    • 持续监控和优化

🚀 下一步学习方向

  • 📈 分类算法:逻辑回归、决策树、随机森林
  • 🧠 神经网络:深度学习基础
  • 📊 无监督学习:聚类、降维
  • 🎯 集成学习:Bagging、Boosting
  • 🔧 特征工程:特征选择、特征构造

🌟 最后的话:机器学习是一个需要持续学习的领域。保持好奇心,多动手实践,你一定能成为优秀的机器学习工程师!加油!


本文档基于sklearn 1.0+版本编写,部分API可能在不同版本中有所差异。

更多推荐