V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
moxiaowei
V2EX  ›  Python

模型在测试集上的准确率卡在 95%上不去,能想的办法都试过了就是不行,都 3 天了,求各位大佬帮我看下有没有优化的方案!跪谢!

  •  
  •   moxiaowei · 6 小时 25 分钟前 · 815 次点击

    使用 SVHN 的数据集进行模型的训练,但是整个模型在训练集上的准确率是一直在上升的,但是到了测试集上就一直卡在 95%,都 3 天了,求各位大佬帮我看下有没有优化的方案!跪谢!

    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.distributed.checkpoint import load_state_dict
    from torch.hub import load_state_dict_from_url
    from torch.nn.modules.loss import _Loss
    from torch.optim import Optimizer
    from torch.utils.data import Dataset, random_split, DataLoader
    import torchvision
    from torchvision.transforms import transforms
    import torchvision.models as m
    import matplotlib.pyplot as plt
    import random
    import gc  # 用于垃圾回收
    from torchinfo import summary
    import numpy as np
    import random
    
    import gc
    
    # 设置随机数种子
    SEED = 420
    random.seed(SEED)
    np.random.seed(SEED)
    torch.manual_seed(SEED)
    torch.cuda.manual_seed(SEED)
    torch.cuda.manual_seed_all(SEED)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    
    # 设置使用 gpu 还是 cpu 进行训练
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    # 定义训练的论述
    epochs = 100
    lr = 0.0001
    
    # 定义数据集需要的参数
    batchSize = 64
    
    # 加载训练集需要的数据转换器
    trainT = transforms.Compose([
        transforms.RandomCrop(28),
        transforms.RandomRotation(degrees=[-15, 15]),
        transforms.ToTensor(),
        transforms.Normalize(mean = [0.4377, 0.4438, 0.4728], std = [0.1980, 0.2010, 0.1970])
    ])
    
    # 加载测试集需要的数据转换器
    testT = transforms.Compose([
        transforms.CenterCrop(28),
        transforms.ToTensor(),
        transforms.Normalize(mean = [0.4377, 0.4438, 0.4728], std = [0.1980, 0.2010, 0.1970])
    ])
    
    # 加载训练集数据
    svhn_train = torchvision.datasets.SVHN(root='C:\\FashionMNIST'
                                           , split="train"
                                           , download=True
                                           , transform=trainT
                                           )
    # 加载测试集数据
    svhn_test = torchvision.datasets.SVHN(root='C:\\FashionMNIST'
                                          , split="test"
                                          , download=True
                                          , transform=testT
                                          )
    
    # 定义神经网络,因为我们的图片的尺寸和样本数量都不是很大,所以选择从 ResNet18 和 Vgg16 中抽取层来构建网络
    resnet18_ = m.resnet18()
    
    
    class MyResNet(nn.Module):  # 这个是基于 ResNet18 构建的网络
        def __init__(self):
            super(MyResNet, self).__init__()
            self.block1 = nn.Sequential(
                nn.Conv2d(3, 64, 3, 1, 1),
                resnet18_.bn1,
                resnet18_.relu
            )
    
            self.block2 = resnet18_.layer2  # 连权重都会复用过来,在 resnet18_ = m.resnet18() 这儿就已经初始化好了权重数据!
            self.block3 = resnet18_.layer3
            self.block4 = resnet18_.layer4 # 从 Resnet18 中哪 layer 新增到自己的模型中
            self.avgpool = resnet18_.avgpool
    
            self.fc = nn.Linear(512, 10, True)
    
        def forward(self, x):
            x = self.block1(x)
            x = self.block2(x)
            x = self.block3(x)
            x = self.block4(x) # 这儿新增一条处理代码
            x = self.avgpool(x)
            x = x.view(-1, 512)
            return self.fc(x)
    
    
    vgg16_ = m.vgg16()
    
    
    class MyVgg(nn.Module):  # 这个是基于 Vgg16 构建的网络
        def __init__(self):
            super(MyVgg, self).__init__()
    
            self.features = nn.Sequential(
                *vgg16_.features[0:9],
                # 使用*是 将 .features[0:9]提取出来的层,全部取出来,一个个放到当前的 Sequential 中,而不是组成一个 Sequential 放到当前的 Sequential 中!
                nn.Conv2d(128, 128, 3, 1, 1),
                nn.ReLU(inplace=True),
                nn.MaxPool2d(2, 2, padding=0, dilation=1, ceil_mode=False)
            )
            self.avgpool = vgg16_.avgpool
    
            self.fc = nn.Sequential(
                nn.Linear(6272, 4096, True),
                *vgg16_.classifier[1:6],
                nn.Linear(4096, 10, True)
            )
    
        def forward(self, x):
            x = self.features(x)
            x = self.avgpool(x)
            x = x.view(-1, 6272)
            x = self.fc(x)
            return x
    
    
    # summary(MyVgg(), input_size=(10, 3, 28, 28)) # 一定要,实例化跑一下,看看有没有问题!
    
    
    class earlyStopping():
    
        def __init__(self, patience=5, tol=0.0005):
            # 当连续 patience=5 次,本轮次的迭代的损失与历史最小的损失的差值大于 0.0005 这个阈值,就会停止训练
            self.patience = patience
            self.tol = tol
            self.counter = 0  # 计数器
            self.lowest_loss = None  # 记录历史最小损失
            self.early_stop = False  # 需要返回是否需要提前停止
    
        def __call__(self, val_loss):  # val_loss 是记录测试集或训练集上一次 epoch 的损失
            if self.lowest_loss is None:
                self.lowest_loss = val_loss
            elif self.lowest_loss - val_loss > self.tol:
                self.lowest_loss = val_loss
                self.counter = 0
            elif self.lowest_loss - val_loss < self.tol:
                self.counter += 1
                print('Notice: Early stopping counter {} of {}'.format(self.counter, self.patience))
    
            if self.counter >= self.patience:
                print('Notice: Early stopping counter Active')
                self.early_stop = True
            return self.early_stop
    
    
    # 定义训练函数
    def fit(net: nn.Module, lossFunc: _Loss, op: Optimizer, trainData: DataLoader, testData: DataLoader, epochs: int):
        transLost = []  # 用于收集每轮训练和测试的结果,用于后面画图表使用
        trainCorrect = []
        testLost = []
        testCorrect = []
        trainedSampleNum = 0
    
        # 初始化 earlystopping 类
        early_stopping = earlyStopping(patience=15, tol=0.00000005)
        # 初始化测试集的历史最高准确率
        test_highest_correct = None
        test_lowest_loss = None
    
        # 获取到整个训练集中的样本数量
        trainTotalNum = trainData.dataset.__len__()
        # 获取到整个测试集中的样本数量
        testTotalNum = testData.dataset.__len__()
    
        for epoch in range(epochs):
            net.train()
            train_loss = 0
            train_correct = 0
            for batch_index, (data, target) in enumerate(trainData):
                data = data.to(device, non_blocking=True)
                target = target.to(device, non_blocking=True).view(data.shape[0])  # 确保标签是 1 维的结构
                trainRes = net(data)  # 经过学习,这儿每个样本会输出 10 个特征结果对应的数据(如果模型中有 softmax ,就是概率),可以用于后续计算准确率
                loss = lossFunc(trainRes, target)
                op.zero_grad()  # 清空优化器上的梯度
                loss.backward()
                op.step()
                # 开始计算准确数,并累加
                yhat = torch.max(trainRes, 1)[1]  # 从 trainRes 一个矩阵中,取出每个样本的最大值和最大值所在的索引,得到[1,2,1,4]这种类型的结果
                correct_num = torch.sum(
                    yhat == target)  # yhat 、target 都是一维张量,使用 == 会挨个对比张量中的元素是否相等,最终得到[False, True, Flase]这样的数据,然后使用 torch.sum 就可以得到一个数字,因为 True 为 1 ,False 为 0 。
                train_correct += correct_num  # 将准备数累加
                # 计算损失,并累加
                train_loss += loss.item()  # 这儿需要得到所有样本的损失的和
                trainedSampleNum += data.shape[0]
                # print("本批次训练损失为:", loss.item() / data.shape[0])
                if (batch_index + 1) % 125 == 0:
                    # 现在进行到了哪个 epoch 、总共要训练多少个样本、已经训练了多少个样本、已训练的样本的百分比
                    print("Epoch{}:{} / {} = ({:.0f}%)".format(
                        epoch + 1,
                        trainedSampleNum,
                        epochs * len(trainData) * batchSize,
                        100 * trainedSampleNum / (epochs * len(trainData) * batchSize)
                    ))
    
            print("-------------------------------")
            avg_correct = (float(train_correct) / trainTotalNum) * 100
            # print("本轮训练平均准确率:", avg_correct)
            trainCorrect.append(avg_correct)
            avg_loss = (float(train_loss) / trainTotalNum) * 100
            # print("本轮训练平均损失率:", avg_loss)
            transLost.append(avg_loss)
    
            del data, target, train_loss, train_correct
            gc.collect()
            torch.cuda.empty_cache()
    
            # 一轮训练结束,就使用测试集进行测试
            net.eval()
            test_loss = 0
            test_correct = 0
            for batch_index, (test_data, test_target) in enumerate(testData):
                with torch.no_grad():
                    test_data = test_data.to(device, non_blocking=True)
                    test_target = test_target.to(device, non_blocking=True).view(test_data.shape[0])  # 确保标签是 1 维的结构
                    testRes = net(test_data)
                    loss = lossFunc(testRes, test_target)
                    # 计算损失,并累加
                    test_loss += loss.item()
                    # 计算准备数,并累加
                    yhat = torch.max(testRes, 1)[1]  # 从 trainRes 一个矩阵中,取出每个样本的最大值和最大值所在的索引,得到[1,2,1,4]这种类型的结果
                    correct_num = torch.sum(
                        yhat == test_target)  # yhat 、target 都是一维张量,使用 == 会挨个对比张量中的元素是否相等,最终得到[False, True, Flase]这样的数据,然后使用 torch.sum 就可以得到一个数字,因为 True 为 1 ,False 为 0 。
                    test_correct += correct_num  # 将准备数累加
    
            avg_test_correct = (float(test_correct) / testTotalNum) * 100
            # print("本轮测试平均准确率:", avg_test_correct)
            testCorrect.append(avg_test_correct)
            avg_test_loss = (float(test_loss) / testTotalNum) * 100
            # print("本轮测试平均损失率:", avg_test_loss)
            testLost.append(avg_test_loss)
    
            print("本轮训练平均准确率:{}, 本轮训练平均损失率: {}, 本轮测试平均准确率:{}, 本轮测试平均损失率:{}".format(
                avg_correct, avg_loss, avg_test_correct, avg_test_loss))
    
            del test_data, test_target, test_loss, test_correct
            gc.collect()
            torch.cuda.empty_cache()
    
            # 如果测试集损失出现新低或者准确率出现新高,就保存在模型的权重,防止中途断电等原因需要从头再来
            if test_highest_correct is None:
                test_highest_correct = avg_test_correct
            if test_highest_correct < avg_test_correct:
                test_highest_correct = avg_test_correct
                torch.save(net.state_dict(), './v6/model-' + str(epoch + 1) + '.pth')
                print("model saved")
    
            # 最好在测试集上使用提前停止,如果使用训练集无法预测过拟合这种情况
            early_stop = early_stopping(avg_test_loss)  # 这儿使用提前停止!
            if early_stop:
                break
        print("mission completed")
        return transLost, trainCorrect, testLost, testCorrect
    
    
    model = MyResNet().to(device)
    # model.load_state_dict(torch.load("./v4/model-49.pth"))
    loss_func = nn.CrossEntropyLoss(reduction='sum')  # 因为我们在训练函数中,在计算损失的时候是计算的每个样本的损失的和,所以这儿需要使用 reduction='sum'
    opt = optim.RMSprop(model.parameters(), lr=lr, weight_decay=0.00005, momentum=0.0001)
    
    train_data = DataLoader(svhn_train, batch_size=batchSize, shuffle=True, drop_last=False, pin_memory=True)
    test_data = DataLoader(svhn_test, batch_size=batchSize, shuffle=False, drop_last=False, pin_memory=True)
    
    # 开始训练
    transLost, trainCorrect, testLost, testCorrect = fit(model, loss_func, opt, train_data, test_data, epochs)
    
    # 训练结果可视化
    plt.plot(transLost, label='train loss')
    plt.plot(testLost, label='test loss')
    plt.plot(trainCorrect, label='train correcct')
    plt.plot(testCorrect, label='test correcct')
    plt.xlabel('Epoch')
    plt.ylabel('CrossEntropy Loss')
    plt.title('Training Loss')
    plt.legend()
    plt.grid(True)
    plt.show()
    13 条回复    2025-07-08 16:57:50 +08:00
    0x636a
        1
    0x636a  
       6 小时 13 分钟前
    1.看看测试集是哪几张图不对,是不是异常图。
    2.试试正则化手段 dropout ,l1 l2 损失,预先训练权重
    不过为啥非要追求 100%呢
    moxiaowei
        2
    moxiaowei  
    OP
       6 小时 7 分钟前
    @0x636a 没有非要 100%,只是有人训练到了 96%以上,我的不行,我很纳闷
    0x636a
        3
    0x636a  
       5 小时 39 分钟前
    @moxiaowei 如果说是同样的代码的话,你试试其他随机数,每个机器上相同随机数表现应该不同的。
    faoisdjioga
        4
    faoisdjioga  
       5 小时 24 分钟前
    要不要加个 Attention 试试呢?简单的加一个加权 Attention layer 就行,或者加一个 transformer encoder layer 呢? 我之前在做 NLP 时候遇到类似的瓶颈,加上 Attention 会好一些
    hwdq0012
        5
    hwdq0012  
       5 小时 14 分钟前
    100%一般是过拟合了
    训练和测试数据集一样比较容易达到 100%

    这个数据集只是 0-9 的分类,基本不存在 label 错误的情况, 否则还有可能是 label 标记错了
    如果不是 label 错了,可以把测试失败的数据 多复制几份,再训练
    opeth
        6
    opeth  
       3 小时 48 分钟前
    1. data augmentation 太少了,用 albumentations 加大数据增广的类别
    2. 模型太小了,换 resnet50 试试
    3. 关掉 early stop ,多跑几个 epochs
    opeth
        7
    opeth  
       3 小时 45 分钟前
    又看了一下,为啥优化器要用 RMSprop ,不用 Adam ?
    学习率下降曲线可以改成 cosine
    batchsize 还能开更大一点吗?
    54lazycat
        8
    54lazycat  
       3 小时 45 分钟前
    @hwdq0012 测试数据拿来训练,测试结果很好看,但实际上没意义
    opeth
        9
    opeth  
       3 小时 45 分钟前
    loss 函数也可以改成更强的分类 loss
    hwdq0012
        10
    hwdq0012  
       3 小时 35 分钟前
    @54lazycat #8 是的,实际企业能达到 98%都很满意了,他们能接受误判的, 训到 100%也没有意义,换个测试数据集可能又达不到了
    moxiaowei
        11
    moxiaowei  
    OP
       3 小时 35 分钟前
    @opeth 好的 大佬 我还在试
    Sawyerhou
        12
    Sawyerhou  
       3 小时 28 分钟前
    这 1%的准确率没有也无所谓吧,还是说你在打比赛要拿名次?
    parad
        13
    parad  
       3 小时 27 分钟前 via Android
    这是你手写的还是 AI 写的呀? AI 写 ML 相关的代码还是不太行。建议看看相关数据&模型的教程,经典 paper 和 baseline 例子参考。
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2920 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 12:25 · PVG 20:25 · LAX 05:25 · JFK 08:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.