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

64 天前
 moxiaowei

使用 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()
2279 次点击
所在节点    Python
16 条回复
0x636a
64 天前
1.看看测试集是哪几张图不对,是不是异常图。
2.试试正则化手段 dropout ,l1 l2 损失,预先训练权重
不过为啥非要追求 100%呢
moxiaowei
64 天前
@0x636a 没有非要 100%,只是有人训练到了 96%以上,我的不行,我很纳闷
0x636a
64 天前
@moxiaowei 如果说是同样的代码的话,你试试其他随机数,每个机器上相同随机数表现应该不同的。
faoisdjioga
64 天前
要不要加个 Attention 试试呢?简单的加一个加权 Attention layer 就行,或者加一个 transformer encoder layer 呢? 我之前在做 NLP 时候遇到类似的瓶颈,加上 Attention 会好一些
hwdq0012
64 天前
100%一般是过拟合了
训练和测试数据集一样比较容易达到 100%

这个数据集只是 0-9 的分类,基本不存在 label 错误的情况, 否则还有可能是 label 标记错了
如果不是 label 错了,可以把测试失败的数据 多复制几份,再训练
opeth
64 天前
1. data augmentation 太少了,用 albumentations 加大数据增广的类别
2. 模型太小了,换 resnet50 试试
3. 关掉 early stop ,多跑几个 epochs
opeth
64 天前
又看了一下,为啥优化器要用 RMSprop ,不用 Adam ?
学习率下降曲线可以改成 cosine
batchsize 还能开更大一点吗?
54lazycat
64 天前
@hwdq0012 测试数据拿来训练,测试结果很好看,但实际上没意义
opeth
64 天前
loss 函数也可以改成更强的分类 loss
hwdq0012
64 天前
@54lazycat #8 是的,实际企业能达到 98%都很满意了,他们能接受误判的, 训到 100%也没有意义,换个测试数据集可能又达不到了
moxiaowei
64 天前
@opeth 好的 大佬 我还在试
Sawyerhou
64 天前
这 1%的准确率没有也无所谓吧,还是说你在打比赛要拿名次?
parad
64 天前
这是你手写的还是 AI 写的呀? AI 写 ML 相关的代码还是不太行。建议看看相关数据&模型的教程,经典 paper 和 baseline 例子参考。
moxiaowei
64 天前
@parad 我手写的
moxiaowei
64 天前
破案了,训练到 96%以上的那个 S..B 用训练集做的测试,感觉到 95%将近 96%已经是极限了,用 Adam 优化器也差不多
moxiaowei
64 天前
@Sawyerhou 不是打比赛,只是在学习

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1143732

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX