DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • 理论基础

  • Py数据分析包

  • 机器学习

  • 深度学习

    • 深度学习基础
    • 激活函数
    • 深度学习框架
    • PyTorch核心组件
    • PyTorch简单分类
    • 损失函数
    • Mnist分类任务
    • 过拟合与欠拟合的处理
      • 欠拟合处理
        • 常用方式
        • 数据增强
      • 过拟合处理
        • Dropout
        • 提前停止训练(早停法)
        • pytorchtools.py
        • 核心用法
        • 正则化
    • 卷积神经网络
  • 华中科大的网课

  • AI
  • 深度学习
DC
2025-01-16
目录

过拟合与欠拟合的处理

在传统机器学习中,我们知道模型在经过训练后,会出现两个病灶 - 过拟合和欠拟合..

  • 过拟合: 模型在训练集中表现很好,在测试集中表现不好 - 过度依赖训练集,泛化能力差
  • 欠拟合: 模型在训练集和测试集中表现都不好

无论是过拟合还是欠拟合,都会影响模型最后的精度.
神经网络模型也会出现 过拟合和欠拟合 两个病灶.. 接下来,我们来阐述下如何解决!


# 欠拟合处理

# 常用方式

通用的欠拟合解决方案

关于神经网络欠拟合的处理, 在"PyTorch简单分类"这篇博客中有所提及, 解决的方案如下方截图所示.

image-20250117105234341

# 数据增强

主要阐述在 图像分类场景中 如何增加样本量..

除了截图中的方案, 还有一个非常重要的解决方案-增加样本量.. (在图像分类任务中,叫作数据增强)

数据增强就是增加数据量, 数据对于机器学习或者深度学习来说非常重要, 有时候拥有更 多的数据胜过拥有一个好的模型.
一般来说更多的数据参与训练, 训练得到的模型就更好.
若数据太少, 而我们构建的神经网络又太复杂的话就比较容易产生欠拟合的现象.

在图像领域, 数据增加的手段经常被使用, 我们可以通过对图片进行一些调整来生成更多图片. 常用的手段如下:

手段 解释
旋转/反射变换(Rotation/reflection) 随机旋转图像一定角度; 改变图像内容的朝向
翻转变换(flip) 沿着水平或者垂直方向翻转图像
缩放变换(zoom) 按照一定的比例放大或者缩小图像
平移变换(shift) 在图像平面上对图像以一定方式进行平移
尺度变换(scale) 对图像按照指定的尺度因子, 进行放大或缩小
颜色变换(color) 对训练集图像的颜色进行一些有规律的调整

用代码实现:

from PIL import Image
from torchvision import transforms  # pip install torchvision 在迁移学习中会常用该模块
import matplotlib.pyplot as plt

img_path = "one.jpg"
img = Image.open(img_path)
# 列表中是该图片进行调整的一系列操作
img_com = transforms.Compose(
        [
            transforms.RandomRotation(45),  # 随机旋转,若设置值为45,则表明随机旋转的角度范围是-45到45度之间    
            transforms.CenterCrop(224),     # 从图像最中心位置进行裁剪,裁剪成224*224大小-通常统一图像大小   
            transforms.RandomHorizontalFlip(p=0.5),  # 随机水平翻转-若设置值为0.5是,则50%概率水平翻转
            transforms.RandomVerticalFlip(p=0.5),    # 随机垂直翻转
            # 参数1为亮度、参数2为对比度、参数3为饱和度、参数4为色相 也是随机的
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
        ]
    )
img = img_com(img)
img
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 过拟合处理

下面阐述神经网络中最常用的三种处理过拟合的方式.

# Dropout

Dropout 是一种用于抵抗过拟合的技术, 它试图改变网络本身来对网络进行优化

image-20250117114141295

如上图所示, Dropout通常应用于神经网络的隐藏层, 使用的时候会临时关闭掉隐藏层一部分的神经元..
即不会给关闭的神经元传递参数.
Dropout是不能影响输入层(神经元个数取决于样本特征维度)和输出层(神经元个数取决于标签类别数)的.

在代码中的具体实现, 就是在设置模型网络结构时, 我们可以给线性层函数设置Dropout参数.
例如 0.4. 意思该线性层中的隐藏层中的神经元 大约有60%是在工作的, 大约40%神经元是不工作的.
因此, Dropout 比较适合应用于只有少量数据但是需要训练复杂模型的场景, 这类场景在图像领域比较常见.
So, Dropout 经常用于图像领域..

示例,有这样一个四层的网络模型, 输入层+隐藏层+隐藏层+输出层, 对该网络模型使用Dropout.

方式一

# - 创建网络结构,使用Dropout机制
class Mnist_NN(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer1 = nn.Linear(784, 128)   # 输出层与隐藏层之间的线性层
        self.dropout1 = nn.Dropout(p=0.5)   # !!添加的代码
        self.layer2 = nn.Linear(128, 256)   # 隐藏层与隐藏层之间的线性层
        self.dropout2 = nn.Dropout(p=0.5)   # !!添加的代码
        self.layer3  = nn.Linear(256, 10)   # 隐藏层与输出层之间的线性层

    def forward(self, x):
        o1 = torch.relu(self.layer1(x))     # 将x输入到 输出层与隐藏层之间的线性层,对该线性层的输出使用relu激活
        o2 = torch.relu(self.layer2(o1))
        y = self.layer3(o2)
        return y
      
model = Mnist_NN().to(device)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

方式二

# - 创建网络结构,使用Dropout机制
class Mnist_NN(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer1 = nn.Sequential(nn.Linear(784,500),
                                    nn.Dropout(p=0.5),  # p=0.5表示50%的神经元不工作
                                    nn.relu())          # 定义激活函数
        
        self.layer2 = nn.Sequential(nn.Linear(500, 300), 
                                    nn.Dropout(p=0.5), 
                                    nn.relu())
        
        self.layer3 = nn.Sequential(nn.Linear(300, 10), 
                                    nn.Softmax(dim=1))

    # 在前向传播的代码块中,就不用指定激活函数了,因为在__init__中已经指定好了
    def forward(self,x):
        x=self.layer1(x)
        x=self.layer2(x)
        x=self.layer3(x)
        return x
    
model = Mnist_NN().to(device)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 提前停止训练(早停法)

我们用大神写好的代码来实现这个功能!! 该代码里大神是用 [损失] 来作为停止迭代的评判标准的.无伤大雅.

核心原理: 在训练模型的时候, 我们往往会设置一个比较大的迭代次数 n.
我们将当前迭代测试准确率 p 与后续m个迭代的结果作对比, 若之后连续 m 个连续迭代周期没有超过该测试集准确率 p,
我们就可以认为 p 不再提高了, 此时便可以提前停止迭代(Early-Stopping).

# pytorchtools.py

大神写好的代码封装在pytorchtools.py里

# pytorchtools.py
import numpy as np
import torch

class EarlyStopping:
    """Early stops the training if validation loss doesn't improve after a given patience."""
    def __init__(self, patience=7, verbose=False, delta=0):
        """
        Args:
            patience (int): How long to wait after last time validation loss improved.
                            Default: 7
            verbose (bool): If True, prints a message for each validation loss improvement. 
                            Default: False
            delta (float): Minimum change in the monitored quantity to qualify as an improvement.
                           Default: 0
        """
        self.patience = patience
        self.verbose = verbose
        self.counter = 0
        self.best_score = None
        self.early_stop = False
        self.val_loss_min = np.Inf
        self.delta = delta

    def __call__(self, val_loss, model):

        score = -val_loss

        if self.best_score is None:
            self.best_score = score
            self.save_checkpoint(val_loss, model)
        elif score < self.best_score + self.delta:
            self.counter += 1
            print(f'EarlyStopping counter: {self.counter} out of {self.patience}')
            if self.counter >= self.patience:
                self.early_stop = True
        else:
            self.best_score = score
            self.save_checkpoint(val_loss, model)
            self.counter = 0

    def save_checkpoint(self, val_loss, model):
        '''Saves model when validation loss decrease.'''
        if self.verbose:
            print(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}) Saving model ...')
        torch.save(model.state_dict(), 'checkpoint.pt')	 # 这里会存储迄今最优模型的参数
        self.val_loss_min = val_loss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 核心用法

用上一篇博客手写字体识别举例.. 只需改动模型 迭代批次训练测试部分的代码即可.
ps: 下述代码中有◎标注的地方就是新增或改变的代码. 重点关注这些代码!

# 提前停止训练相关变量声明
# 当连续2次训练周期中都没有变得更好(损失都没降低)时,停止模型训练,以防止模型过拟合
# ◎ 以下三行为新增代码
from pytorchtools import EarlyStopping  
patience = 2  
early_stopping = EarlyStopping(patience, verbose=True)

num_epochs = 10  # 10次迭代
for step in range(num_epochs):
    # ■ 训练阶段
    model.train()
    batch_loss = []
    batch_acc = []
    for xb,yb in train_dl:
        xb = xb.to(device)
        yb = yb.to(device)

        prediction = model(xb)
        loss = criterion(prediction, yb)
        loss.backward()
        opt.step()
        opt.zero_grad()
        
        batch_loss.append(loss.item())
        train_acc = accuracy(prediction, yb) 
        batch_acc.append(train_acc)

    # ◎ 这里改变了下,每次迭代都打印
    if (step+1) % 1 == 0:
        print(f'Epoch [{step+1}/{num_epochs}]')
        print(f'Train Loss: {np.mean(batch_loss):.4f}, Train Accuracy: {np.mean(batch_acc):.4f}')

    # ■ 测试阶段
    model.eval()
    batch_loss_test = []
    batch_acc_test = []
    for xb,yb in test_dl:
        xb = xb.to(device)
        yb = yb.to(device)
        with torch.no_grad():
            test_outputs = model(xb)
            test_loss = criterion(test_outputs, yb)

            batch_loss_test.append(test_loss.item())
            test_acc = accuracy(test_outputs, yb)  
            batch_acc_test.append(test_acc)

    # ◎ 这里改变了下,每次迭代都打印
    if (step+1) % 1 == 0:
        print(f'Epoch [{step+1}/{num_epochs}]')
        print(f'Test Loss: {np.mean(batch_loss_test):.4f}, Test Accuracy: {np.mean(batch_acc_test):.4f}')

    # ◎ 以下代码为新增代码
    # - 提前停止迭代操作
    mean_loss_test = np.mean(batch_loss_test)
    early_stopping(mean_loss_test, model)  # 本质就是调用EarlyStopping类中的__call__方法
    # - 若满足 early stopping 要求
    if early_stopping.early_stop:
        print("Early stopping-------------------------------------")
        break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
image-20250117130253511

# 正则化

在传统机器学习和神经网络中, 都可通过正则化来解决过拟合的病灶.

正则化也叫作规范化, 通常用得比较多的方式是 L1 正则化和 L2 正则化, 跟传统机器学习中的正则化一样.

  • 在pytorch中使用正则化的方式非常简单,只需要在优化器函数中使用weight_decay参数即可
    • 该参数的值表示L2正则化的惩罚系数, 一般设置为1、5、10(根据需要设置, 默认为0)
      别问为啥, 设置 1、5、10就对了..
    • 参数取值范围:
      • 正数 - 通常取一个较小的正数, 例如 1e−5, 1e−2 等. 较大的数值会导致学习速率显著下降,从而可能影响模型的训练效果
      • 0 - 表示不使用权重衰减
      • 负数 - 一般不使用负数, 因为负的权重衰减没有实际意义且可能导致不稳定的训练过程.
    • 适用场景
      • 过拟合: 当模型在训练集上表现很好但在验证集或测试集上表现较差时, 可能是过拟合现象.
        此时引入权重衰减可以帮助减少模型复杂度, 防止过拟合.
      • 高维数据: 对于高维数据集, 权重衰减可以有效减少参数的数量, 提高模型的泛化能力.
      • 深层神经网络: 在深层神经网络中, 权重衰减有助于避免梯度爆炸问题, 并使模型更稳定.
      • 小数据集: 当数据集较小时, 模型容易过拟合. 引入权重衰减可以帮助提升模型的泛化能力.

注意: pytorch中的优化函数L2正则化默认对所有网络参数进行惩罚, 且只能实现L2正则化.
如需惩罚指定网络层参数或采用L1正则化, 只能自己定义.

optimizer=optim.SGD(model.parameters(),LR,weight_decay=5)  # 优化器 1 5 10 值越小对权重系数惩罚越小
1

在神经网络结构比较复杂, 训练数 据量比较少的时候, 使用正则化效果会比较好.
如果网络不算太复杂的话, 任务比较简单的时候, 使用正则化可能准确率反而会下降.
像在手写识别中,使用它,准确率会下降..


model.train()、model.train() 没那么简单, 不仅仅是训练测试的标识, 跟过拟合也有关系, 后续的项目会涉及到..


Mnist分类任务
卷积神经网络

← Mnist分类任务 卷积神经网络→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式