在机器学习建模过程中,新手最容易踩的坑就是「欠拟合」和「过拟合」——明明模型原理懂了、代码也能跑通,但测试集准确率上不去,要么拟合不够、要么拟合过度,甚至出现“训练集满分、测试集翻车”的情况。

  本文专门针对逻辑回归场景,从「欠拟合/过拟合的识别」「核心原因」「解决方法」三个维度,重点拆解工业界最常用的L1、L2正则化原理+实操,结合Python sklearn代码演示,通俗易懂、可直接复用,帮你快速避开拟合陷阱,提升模型泛化能力,适合机器学习初学者和入门实践者收藏学习!

一、先搞懂:什么是欠拟合、过拟合?

不管是线性回归,逻辑回归,还是决策树、随机森林,欠拟合和过拟合的核心本质是「模型复杂度与数据规律的匹配度失衡」——简单说,就是模型“学不会”或者“学太死”,我们用最通俗的语言+逻辑回归场景案例,帮你区分清楚。

1.1 欠拟合(Underfitting):模型“学不会”数据规律

核心定义

欠拟合是指模型的复杂度太低,无法捕捉到数据中的核心规律,不仅在测试集上表现差,在训练集上的表现也不好——相当于老师讲了一堆知识点,学生连基础内容都没学会,考试(训练集)和实战(测试集)都考不及格。

实战案例(线性回归演示,见下文)

生成含噪声的非线性数据(y = 0.5x² + x + 2 + 噪声),用简单线性回归(仅用x作为特征)建模,模型只能拟合出一条直线,无法捕捉数据的二次曲线规律,导致拟合效果差、均方误差较大。

关键识别特征(必记)

  • 训练集准确率低、测试集准确率也低(两者差距≤5%);

  • 模型预测结果“一刀切”,无法区分边缘样本(如逻辑回归的概率输出大多集中在0或1,很少有0.3-0.7之间的中间值);

  • 增加训练数据量,模型性能几乎没有提升。

核心原因

  1. 模型复杂度不足:逻辑回归本身是线性模型,若数据中存在非线性规律(如特征与类别概率呈非线性关联),未做特征工程,模型无法拟合;

  2. 特征不足或质量差:用于建模的特征太少、特征与目标变量相关性弱,或特征存在大量噪声,模型无法捕捉有效规律;

  3. 训练不充分:梯度下降迭代次数不足(max_iter太小),模型未找到最优权重,还没“学会”数据规律就停止训练。

1.2 过拟合(Overfitting):模型“学太死”,泛化能力差

核心定义

过拟合是指模型的复杂度太高,不仅捕捉到了数据中的核心规律,还把训练数据中的噪声、异常值当成了“规律”去拟合——相当于学生死记硬背考试题库,考试(训练集)能考满分,但遇到新题目(测试集)就翻车,泛化能力极差。

实战案例(线性回归演示,见下文)

同样使用上述非线性数据,建模时加入x的1次到10次高次项,用线性回归拟合,模型会过度贴合每个数据点(包括噪声点),拟合曲线变得极度复杂,虽然训练集均方误差极小,但无法适配新数据,泛化能力极差。

关键识别特征(必记)

  • 训练集准确率极高(≥95%),但测试集准确率明显偏低(两者差距≥10%);

  • 逻辑回归的特征系数绝对值极大(部分系数达到几十、上百),说明模型过度依赖某些特征;

  • 增加测试数据量,测试集准确率会明显提升(因为新数据中的噪声与训练集不同,模型“死记硬背”的规律失效)。

核心原因

  1. 模型复杂度过高:逻辑回归中,特征过多、未做特征筛选,或未使用正则化,导致模型过度拟合训练数据;

  2. 训练数据不足或失衡:训练样本太少,或正负类样本比例悬殊,模型容易把少量样本的规律当成普遍规律;

  3. 训练数据存在噪声/异常值:未对异常值、噪声数据进行处理,模型拟合了无效信息,干扰了核心规律的捕捉。

1.3 欠拟合vs过拟合 核心对比

对比维度

欠拟合

过拟合

核心表现

训练集、测试集准确率都低

训练集准确率高,测试集准确率低

模型复杂度

过低(无法捕捉数据规律)

过高(拟合噪声和异常值)

逻辑回归特征系数

系数绝对值普遍偏小,部分为0

系数绝对值极大,波动剧烈

解决核心思路

提升模型复杂度、补充有效特征

降低模型复杂度、抑制过拟合(正则化为主)

我们可以通过一张图来更清晰的观测到欠拟合和过拟合的区别,如下:

二、欠拟合的原因以及解决办法

欠拟合产生原因: 学习到数据的特征过少

2.1 提升模型复杂度(适配逻辑回归)

逻辑回归是线性模型,默认只能拟合线性规律,若数据存在非线性关联,可通过以下2种方式提升复杂度(优先选第一种,简单且不易过拟合):

  • 特征工程(核心):构造非线性特征,如对连续特征做多项式扩展(比如把单个连续特征做平方处理、把两个不同的连续特征做相乘处理),或对类别特征做交叉特征,让线性模型能捕捉到非线性规律;

  • 调整模型参数:逻辑回归中,可适当增加迭代次数(max_iter),确保模型充分训练,找到最优权重(避免训练不充分导致的欠拟合)。

2.2 补充有效特征(最直接)

若建模时使用的特征太少、相关性弱,可补充与目标变量强相关的特征,比如做肿瘤分类时,补充“肿瘤纹理、光滑度、凹陷度”等核心特征,让模型有足够的信息捕捉规律。

小技巧:用相关性分析(如Pearson相关系数)筛选特征,保留与目标变量相关性绝对值≥0.3的特征,剔除无关特征,避免特征冗余的同时,补充有效信息。

2.3 优化训练数据质量

若训练数据中存在大量噪声、异常值,会干扰模型对核心规律的捕捉,导致欠拟合,可做以下处理:

  • 异常值处理:用箱线图、Z-score方法识别异常值,根据场景选择删除、替换(如用均值、中位数替换);

  • 数据清洗:剔除重复样本、填补缺失值(连续特征用均值/中位数,类别特征用众数),确保数据的完整性和准确性。

2.4 实操演示(完整代码,可直接运行)

以下代码包含「欠拟合案例」「拟合良好案例」,对比演示欠拟合的解决过程,注释详细,新手可直接复制运行:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error  # 计算均方误差


def dm01_欠拟合():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化 线性回归模型.
    estimator = LinearRegression()
    # 3. 训练模型
    X = x.reshape(-1, 1)
    estimator.fit(X, y)

    # 4. 模型预测.
    y_predict = estimator.predict(X)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估(欠拟合时均方误差较大)
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)           # 散点图(实际数据点)
    plt.plot(x, y_predict, color='r')   # 折线图(预测值, 拟合回归线)
    plt.title("欠拟合案例(简单线性回归拟合二次数据)")
    plt.show()                  # 具体的绘图


def dm02_模型ok():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化 线性回归模型.
    estimator = LinearRegression()
    # 3. 训练模型(增加x²高次项,解决欠拟合)
    X = x.reshape(-1, 1)
    X2 = np.hstack([X, X ** 2])  # 拼接x和x²,补充非线性特征
    estimator.fit(X2, y)

    # 4. 模型预测.
    y_predict = estimator.predict(X2)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估(拟合良好时均方误差显著降低)
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图(实际数据点)
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    plt.title("拟合良好案例(补充x²高次项,解决欠拟合)")
    plt.show()  # 具体的绘图


# 运行代码,对比欠拟合与拟合良好的效果
if __name__ == '__main__':
    dm01_欠拟合()
    dm02_模型ok()

第一种情况:欠拟合

第二种情况:添加二次项,绘制图像,正好拟合:

分析:运行后可观察到:欠拟合案例均方误差较大(约 3.0 左右),拟合良好案例(补充 x²)均方误差显著降低(约 1.0 左右),说明补充高次项可有效解决欠拟合;

三、过拟合的核心解决方法:L1、L2正则化(工业界首选)

过拟合的解决思路是「降低模型复杂度,抑制模型对噪声的拟合」,其中L1、L2正则化是逻辑回归中最常用、最有效的方法——无需删除特征、无需大量调整数据,只需在模型中添加正则化项,就能快速抑制过拟合,还能保留模型的可解释性。

先明确核心逻辑:正则化的本质是「对模型权重施加惩罚」,让权重绝对值尽可能小,避免某些特征被过度依赖,从而降低模型复杂度,提升泛化能力。

注意:sklearn中,用参数C表示正则化强度,C的值越小,正则化的惩罚力度越强(模型越简单,不易过拟合);C的值越大,正则化的惩罚力度越弱(模型越复杂,易过拟合),记准这个规律,避免调参踩坑!

3.1 过拟合实战演示(可直接运行)

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error  # 计算均方误差


def dm03_过拟合():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化 线性回归模型.
    estimator = LinearRegression()
    # 3. 训练模型(加入过多高次项,导致过拟合)
    X = x.reshape(-1, 1)
    # hstack() 函数用于将多个数组在行上堆叠起来, 即: 数据增加高次项.
    X3 = np.hstack([X, X**2, X**3, X**4, X**5, X**6, X**7, X**8, X**9, X**10])
    estimator.fit(X3, y)

    # 4. 模型预测.
    y_predict = estimator.predict(X3)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估(过拟合时训练集均方误差极小)
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图(实际数据点)
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    plt.title("过拟合案例(加入过多高次项,过度拟合噪声)")
    plt.show()  # 具体的绘图


# 运行代码,观察过拟合现象
if __name__ == '__main__':
    dm03_过拟合()

第三种情况:过拟合(再次加入高次项,绘制图像,观察均方误差结果):

3.2 L1正则化(Lasso Regression,拉索回归)—— 适合特征筛选

核心定义

假设L(W)是未加正则项的原始损失,λ(lambda)是超参数,用于控制正则化项的惩罚力度,L1正则化是在逻辑回归原始损失的基础上,加入“所有特征权重的绝对值和”作为惩罚项,最终的损失函数数学公式为:

公式解读:式中L(W)为原始损失,λ控制惩罚强度,n为特征数量,|wᵢ|为第i个特征权重的绝对值,正则化项通过惩罚权重的绝对值,可将无关特征的权重压缩至0。

核心特点(必记)

  • 惩罚权重绝对值:会将不重要特征的权重压缩到0,实现「自动特征筛选」(相当于删除无关特征);

  • 适合场景:特征数量多、存在大量无关特征的场景(如文本分类、多特征风控建模);

  • 注意事项:对异常值敏感,若数据存在较多异常值,优先用L2正则化;

  • sklearn参数:penalty='l1',且solver需选择支持L1的(如liblinear、saga)。

通俗解读

L1正则化相当于给权重“做减法”,把不重要的特征权重直接变成0,只保留对结果影响大的特征——比如做用户流失预测时,有50个特征,L1正则化会自动筛选出10个核心特征,删除40个无关特征,简化模型的同时,抑制过拟合。

3.3 L2正则化(Ridge Regression,岭回归)—— 最常用,工业界首选

核心定义​

假设L(W)是未加正则项的原始损失,λ(lambda)是超参数,用于控制正则化项的惩罚力度,L2正则化是在逻辑回归原始损失的基础上,加入“所有特征权重的平方和”作为惩罚项,最终的损失函数数学公式为:

公式解读:式中L(W)为原始损失,λ控制惩罚强度,n为特征数量,wᵢ为第i个特征的权重,正则化项通过惩罚权重的平方,约束权重取值不能过大。

核心特点(必记)

  • 惩罚权重平方:会将权重压缩到接近0,但不会等于0(所有特征都会被保留);

  • 鲁棒性强:对异常值不敏感,适合大多数逻辑回归场景(如风控、医疗诊断);

  • 核心作用:抑制过拟合,同时保留所有特征的影响,不改变模型的线性特性;

  • sklearn参数:penalty='l2'(逻辑回归默认参数,无需手动设置)。

通俗解读

L2正则化相当于给权重“戴了一个枷锁”,让所有权重都不能太大,避免某个特征被过度依赖——比如做肿瘤分类时,不会因为“某个异常样本的半径特征”,就让半径的权重变得极大,从而忽略其他核心特征。

3.4 L1 vs L2 正则化 核心对比(表格总结,实操选型必备)

对比维度

L1正则化(Lasso)

L2正则化(Ridge)

惩罚项

权重绝对值和(公式:L = L(W) + \lambda \cdot \sum_{i=1}^{n} |w_i|

权重平方和(公式:L = L(W) + \lambda \cdot \sum_{i=1}^{n} w_i^2

权重影响

部分权重压缩至0(特征筛选)

权重压缩至接近0(不删除特征)

对异常值敏感

敏感(不适合异常值多的场景)

不敏感(工业界首选)

sklearn参数

penalty='l1',solver='liblinear/saga'

penalty='l2'(默认),solver无特殊限制

适用场景

特征多、存在无关特征(需筛选特征)

大多数场景(抑制过拟合,保留所有特征)

四、Python sklearn 正则化实战

L1正则化实战演示(完整代码,可直接运行)

用L1正则化解决上文的过拟合问题,观察模型权重变化、均方误差和拟合效果

from sklearn.linear_model import Lasso  # L1正则
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error  # 计算均方误差


def dm04_模型过拟合_L1正则化():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化L1正则化模型, 做实验: alpha惩罚力度越来越大, 权重越来越小.
    estimator = Lasso(alpha=0.005)  # alpha越小,惩罚越弱;越大,惩罚越强
    # 3. 训练模型(沿用过拟合案例的高次项特征,用L1正则化抑制过拟合)
    X = x.reshape(-1, 1)
    # hstack() 函数用于将多个数组在行上堆叠起来, 即: 数据增加高次项.
    X3 = np.hstack([X, X**2, X**3, X**4, X**5, X**6, X**7, X**8, X**9, X**10])
    estimator.fit(X3, y)
    print(f'权重: {estimator.coef_}')  # 可观察到无关高次项的权重被压缩至0(特征筛选)

    # 4. 模型预测.
    y_predict = estimator.predict(X3)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估(正则化后,均方误差略高于过拟合,但泛化能力更强)
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图(实际数据点)
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    plt.title("L1正则化解决过拟合案例")
    plt.show()  # 具体的绘图


# 运行代码,观察L1正则化抑制过拟合的效果
if __name__ == '__main__':
    dm04_模型过拟合_L1正则化()

在进行L1正则化后效果如图所示:

分析:运行后可观察到:无关高次项(如 x^3、x^4)的权重被压缩至 0,仅保留 x、x² 的权重,实现特征筛选,均方误差略高于过拟合案例,但泛化能力更强”

L2正则化实战演示(完整代码,可直接运行)

L2正则化(Ridge)解决过拟合的代码,与L1正则化对比使用,完整覆盖两种正则化的实操:

from sklearn.linear_model import Ridge  # 岭回归 L2正则
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error  # 计算均方误差


def dm05_模型过拟合_L2正则化():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化L2正则化模型, 做实验: alpha惩罚力度越来越大, k值越来越小.
    estimator = Ridge(alpha=0.005)
    # 3. 训练模型
    X = x.reshape(-1, 1)
    # hstack() 函数用于将多个数组在行上堆叠起来, 即: 数据增加高次项.
    X3 = np.hstack([X, X ** 2, X ** 3, X ** 4, X ** 5, X ** 6, X ** 7, X ** 8, X ** 9, X ** 10])
    estimator.fit(X3, y)
    print(f'权重: {estimator.coef_}')

    # 4. 模型预测.
    y_predict = estimator.predict(X3)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    plt.show()  # 具体的绘图

# 运行代码,对比L1和L2正则化的效果
if __name__ == '__main__':
    dm05_模型过拟合_L2正则化()

在进行L2正则化后如图所示:

五、常见踩坑点总结(新手必看,避免走弯路)

  • 踩坑1:误解alpha的含义——alpha越大,正则化惩罚越强(模型越简单);alpha越小,惩罚越弱(模型越复杂),记反会导致调参无效;

  • 踩坑2:混淆L1和L2对应的模型——L1对应Lasso、L2对应Ridge,导入时不要混淆;

  • 踩坑3:未做特征预处理直接用正则化——特征量纲差异会导致权重惩罚不均,建议先做标准化/归一化;

  • 踩坑4:过度依赖正则化——正则化只能抑制过拟合,若数据存在大量噪声、异常值,需先做数据清洗,再用正则化;

  • 踩坑5:欠拟合时用正则化——欠拟合是模型拟合能力不足,正则化会进一步降低模型复杂度,导致欠拟合更严重(欠拟合需提升模型复杂度,而非用正则化);

  • 踩坑6:过拟合时仅增加alpha——alpha过大会导致模型欠拟合,需合理调整alpha(可从0.001、0.01、0.1、1.0逐步尝试)。

六、总结

本文通过完整实战代码,详细拆解了欠拟合、过拟合的识别方法、核心原因,重点讲解了L1、L2正则化的原理、区别和实操方法,帮你快速避开拟合陷阱。

最后用一句话总结核心要点,方便大家记忆和实操:

欠拟合:学不会,需提升模型复杂度(补充高次项等特征);过拟合:学太死,需用正则化抑制(L2首选,L1适合特征筛选);正则化调参看alpha,越大惩罚越强,泛化优先选平衡。

文中所有代码均可直接复制运行,建议大家实际操作一遍,对比欠拟合、过拟合、正则化后的效果,加深理解。如果觉得本文对你有帮助,欢迎点赞、收藏、关注,后续会持续更新机器学习实战技巧,助力大家从新手快速成长为实操达人!

更多推荐