Pytorch基于Resnet18在CIFAR100数据集上的准确率较低。

3

我正在CIFAR100数据集上训练一个ResNet18。大约经过50次迭代后,验证精度收敛在34%左右。而训练精度几乎达到了100%。

我怀疑这可能是一种过拟合现象,因此我应用了像RandomHorizontalFlipRandomRotation这样的数据增强手段,这使得验证精度收敛在40%左右。

我还尝试了衰减学习率[0.1, 0.03, 0.01, 0.003, 0.001],每50次迭代进行一次衰减。衰减学习率似乎并没有提高性能。

有人说,在CIFAR100上使用Resnet可以获得70%~80%的准确度。还有什么其他的技巧可以应用吗?或者我的实现有什么问题?同样的代码在CIFAR10上可以达到约80%的准确度。

我整个训练和评估的代码如下:

import torch
from torch import nn
from torch import optim
from torch.utils.data import DataLoader
from torchvision.models import resnet18
from torchvision.transforms import Compose, ToTensor, RandomHorizontalFlip, RandomRotation, Normalize
from torchvision.datasets import CIFAR10, CIFAR100
import os
from datetime import datetime
import matplotlib.pyplot as plt


def draw_loss_curve(histories, legends, save_dir):
    os.makedirs(save_dir, exist_ok=True)
    for key in histories[0][0].keys():
        if key != "epoch":
            plt.figure()
            plt.title(key)
            for history in histories:
                x = [h["epoch"] for h in history]
                y = [h[key] for h in history]
                # plt.ylim(ymin=0, ymax=3.0)
                plt.plot(x, y)
            plt.legend(legends)
            plt.savefig(os.path.join(save_dir, key + ".png"))


def cal_acc(out, label):
    batch_size = label.shape[0]
    pred = torch.argmax(out, dim=1)
    num_true = torch.nonzero(pred == label).shape[0]
    acc = num_true / batch_size
    return torch.tensor(acc)


class LrManager(optim.lr_scheduler.LambdaLR):
    def __init__(self, optimizer, lrs):
        def f(epoch):
            rate = 1
            for k in sorted(lrs.keys()):
                if epoch >= k:
                    rate = lrs[k]
                else:
                    break
            return rate
        super(LrManager, self).__init__(optimizer, f)


def main(cifar=100, epochs=250, batches_show=100):
    if torch.cuda.is_available():
        device = "cuda"
    else:
        device = "cpu"
        print("warning: CUDA is not available, using CPU instead")
    dataset_cls = CIFAR10 if cifar == 10 else CIFAR100
    dataset_train = dataset_cls(root=f"data/{dataset_cls.__name__}/", download=True, train=True,
                                transform=Compose([RandomHorizontalFlip(), RandomRotation(15), ToTensor(), Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))]))
    dataset_val = dataset_cls(root=f"data/{dataset_cls.__name__}/", download=True, train=False,
                              transform=Compose([ToTensor(), Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))]))
    loader_train = DataLoader(dataset_train, batch_size=128, shuffle=True)
    loader_val = DataLoader(dataset_val, batch_size=128, shuffle=True)
    model = resnet18(pretrained=False, num_classes=cifar).to(device)
    optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-5)
    lr_scheduler = LrManager(optimizer, {0: 1.0, 50: 0.3, 100: 0.1, 150: 0.03, 200: 0.01})
    criterion = nn.CrossEntropyLoss()

    history = []
    model.train()
    for epoch in range(epochs):
        print("-------------------  TRAINING  -------------------")
        loss_train = 0.0
        running_loss = 0.0
        acc_train = 0.0
        running_acc = 0.0
        for batch, data in enumerate(loader_train, 1):
            img, label = data[0].to(device), data[1].to(device)
            optimizer.zero_grad()
            pred = model(img)
            loss = criterion(pred, label)
            loss.backward()
            optimizer.step()

            running_loss += loss.item()
            loss_train += loss.item()
            acc = cal_acc(pred, label)
            running_acc += acc.item()
            acc_train += acc.item()

            if batch % batches_show == 0:
                print(f"epoch: {epoch}, batch: {batch}, loss: {running_loss/batches_show:.4f}, acc: {running_acc/batches_show:.4f}")
                running_loss = 0.0
                running_acc = 0.0
        loss_train = loss_train / batch
        acc_train = acc_train / batch
        lr_scheduler.step()

        print("------------------- EVALUATING -------------------")
        with torch.no_grad():
            running_acc = 0.0
            for batch, data in enumerate(loader_val, 1):
                img, label = data[0].to(device), data[1].to(device)
                pred = model(img)
                acc = cal_acc(pred, label)
                running_acc += acc.item()
            acc_val = running_acc / batch
            print(f"epoch: {epoch}, acc_val: {acc_val:.4f}")

        history.append({"epoch": epoch, "loss_train": loss_train, "acc_train": acc_train, "acc_val": acc_val})
    draw_loss_curve([history], legends=[f"resnet18-CIFAR{cifar}"], save_dir=f"history/resnet18-CIFAR{cifar}[{datetime.now()}]")


if __name__ == '__main__':
    main()
3个回答

11

torchvision.models 中的 Resnet18 是 ImageNet 实现。因为 ImageNet 样本比 CIFAR10/100 大得多(224x224 vs. 32x32),因此第一层被设计为强烈下采样输入(“干线网络”)。这导致在小的 CIFAR10/100 图像上丢失了许多有价值的信息。

为了在 CIFAR10 上获得良好的准确性,作者使用了与原始论文中描述的不同的网络结构: https://arxiv.org/pdf/1512.03385.pdf 并在这篇文章中解释: https://towardsdatascience.com/resnets-for-cifar-10-e63e900524e0

您可以从此存储库下载用于 CIFAR10 的 Resnet: https://github.com/akamaster/pytorch_resnet_cifar10


2
使用准确率作为性能指标来评估具有大量类别(例如100个)的数据集是不公平的。这就是为什么人们使用topk准确率的原因。例如,如果所有正确的预测总是在前5个预测类别中,则top-5准确率将达到100%。这就是为什么在ImageNet(1000个类别)上训练的模型要使用top-5准确率进行评估的原因。
对于具有100个类别的正常准确率(即top-1准确率),我认为34%已经相当不错了。
所以,这里似乎没有问题。
此外,如果测试得分为34%,而训练得分为100%,那么它确实是一个非常强的过拟合现象。

1
这是我会尝试的方法:
  • 简化模型:减少卷积层并加入批量归一化,可能在末尾加入更多的全连接层,在它们之间使用dropout
  • 手动更改学习率:从0.01开始或每次val-acc不再降低时,中断程序,将lr除以2并继续训练(必须保存每个epoch的模型检查点)
  • 尝试少于100类并逐步增加
  • 使用model.eval()计算val-acc而非model.train()以去除dropout和批量归一化

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接