持续创作,加快成长!这是我参与「日新计划 10 月更文挑战」的第28天,点击查看活动详情

在上一篇文章中完结了前期的准备工作,见链接: MaxViT实战:运用MaxViT实现图像分类使命(一) 这篇首要是解说怎么练习和测验

练习

完结上面的进程后,就开端train脚本的编写,新建train.py.

导入项目运用的库

import json
import os
import shutil
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torch.utils.data.distributed
import torchvision.transforms as transforms
from timm.utils import accuracy, AverageMeter
from sklearn.metrics import classification_report
from timm.data.mixup import Mixup
from timm.loss import SoftTargetCrossEntropy
from torchvision import datasets
from timm.models.maxxvit import maxvit_tiny_224
torch.backends.cudnn.benchmark = False
import warnings
warnings.filterwarnings("ignore")
from ema import EMA

设置大局参数

设置学习率、BatchSize、epoch等参数,判别环境中是否存在GPU,假如没有则运用CPU。主张运用GPU,CPU太慢了。

if __name__ == '__main__':
    #创立保存模型的文件夹
    file_dir = 'checkpoints/MaxViT'
    if os.path.exists(file_dir):
        print('true')
        os.makedirs(file_dir,exist_ok=True)
    else:
        os.makedirs(file_dir)
    # 设置大局参数
    model_lr = 1e-4
    BATCH_SIZE = 16
    EPOCHS = 1000
    DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
    use_amp = True  # 是否运用混合精度
    use_dp=False #是否敞开dp方式的多卡练习
    classes = 12
    resume = False
    CLIP_GRAD = 5.0
    model_path = 'best.pth'
    Best_ACC = 0 #记载最高得分
    use_ema=True
    ema_epoch=32

设置寄存权重文件的文件夹,假如文件夹存在删去再建立。

接下来,查看大局参数:

model_lr:学习率,根据实际情况做调整。

BATCH_SIZE:batchsize,根据显卡的巨细设置。

EPOCHS:epoch的个数,一般300够用。

use_amp:是否运用混合精度。

classes:类别个数。

resume:是否接着前次模型持续练习。

model_path:模型的途径。假如resume设置为True时,就选用model_path界说的模型持续练习。

CLIP_GRAD:梯度的最大范数,在梯度裁剪里设置。

Best_ACC:记载最高ACC得分。 use_ema:是否运用ema ema_epoch:多个epoch运用一次ema,在use_ema为True的情况下有效。

图像预处理与增强

数据处理比较简单,加入了Cutout、做了Resize和归一化,界说Mixup函数。

这里注意下Resize的巨细,因为选用的MaxViT模型输入是224224的巨细,所以要Resize为224224。

   # 数据预处理7
    transform = transforms.Compose([
        transforms.RandomRotation(10),
        transforms.GaussianBlur(kernel_size=(5,5),sigma=(0.1, 3.0)),
        transforms.ColorJitter(brightness=0.5, contrast=0.5, saturation=0.5),
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.44127703, 0.4712498, 0.43714803], std= [0.18507297, 0.18050247, 0.16784933])
    ])
    transform_test = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.44127703, 0.4712498, 0.43714803], std= [0.18507297, 0.18050247, 0.16784933])
    ])
    mixup_fn = Mixup(
        mixup_alpha=0.8, cutmix_alpha=1.0, cutmix_minmax=None,
        prob=0.1, switch_prob=0.5, mode='batch',
        label_smoothing=0.1, num_classes=classes)

读取数据

运用pytorch默许读取数据的方式,然后将dataset_train.class_to_idx打印出来,猜测的时候要用到。

将dataset_train.class_to_idx保存到txt文件或者json文件中。

    # 读取数据
    dataset_train = datasets.ImageFolder('data/train', transform=transform)
    dataset_test = datasets.ImageFolder("data/val", transform=transform_test)
    with open('class.txt', 'w') as file:
        file.write(str(dataset_train.class_to_idx))
    with open('class.json', 'w', encoding='utf-8') as file:
        file.write(json.dumps(dataset_train.class_to_idx))
    # 导入数据
    train_loader = torch.utils.data.DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
    test_loader = torch.utils.data.DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=False)

class_to_idx的结果:

{‘Black-grass’: 0, ‘Charlock’: 1, ‘Cleavers’: 2, ‘Common Chickweed’: 3, ‘Common wheat’: 4, ‘Fat Hen’: 5, ‘Loose Silky-bent’: 6, ‘Maize’: 7, ‘Scentless Mayweed’: 8, ‘Shepherds Purse’: 9, ‘Small-flowered Cranesbill’: 10, ‘Sugar beet’: 11}

设置模型

  • 设置loss函数,train的loss为:SoftTargetCrossEntropy,val的loss:nn.CrossEntropyLoss()。
  • 设置模型为maxvit_tiny_224,num_classes设置为12。假如resume为True,则加载模型接着前次练习。
  • 优化器设置为adamW。
  • 学习率调整策略挑选为余弦退火。
  • 敞开混合精度练习,声明pytorch自带的混合精度 torch.cuda.amp.GradScaler()。
  • 检测可用显卡的数量,假如大于1,而且敞开多卡练习的情况下,则要用torch.nn.DataParallel加载模型,敞开多卡练习。
# 实例化模型而且移动到GPU
    criterion_train = SoftTargetCrossEntropy()
    criterion_val = torch.nn.CrossEntropyLoss()
    #设置模型
    model_ft = maxvit_tiny_224(pretrained=True)
    num_ftrs = model_ft.head.fc.in_features
    model_ft.head.fc = nn.Linear(num_ftrs, classes)
    if resume:
        model_ft = torch.load(model_path)
    model_ft.to(DEVICE)
    # 挑选简单暴力的Adam优化器,学习率调低
    optimizer = optim.AdamW(model_ft.parameters(),lr=model_lr)
    cosine_schedule = optim.lr_scheduler.CosineAnnealingLR(optimizer=optimizer, T_max=20, eta_min=1e-6)
    if use_amp:
        scaler = torch.cuda.amp.GradScaler()
    if torch.cuda.device_count() > 1 and use_dp:
        print("Let's use", torch.cuda.device_count(), "GPUs!")
        model_ft = torch.nn.DataParallel(model_ft)
    if use_ema:
        ema = EMA(model_ft, 0.999)
        ema.register()

注:torch.nn.DataParallel方式,默许不能敞开混合精度练习的,假如想要敞开混合精度练习,则需要在模型的forward前面加上@autocast()函数。

MaxViT实战:使用MaxViT实现图像分类任务(二)

假如不敞开混合精度则要将@autocast()去掉,不然loss一直试nan。

界说练习和验证函数

练习函数

练习的首要进程:

1、运用AverageMeter保存自界说变量,包括loss,ACC1,ACC5。

2、判别迭代的数据是否是奇数,因为mixup_fn只能接受偶数,所以假如不是偶数则要减去一位,让其变成偶数。但是有或许最终一次迭代只有一条数据,减去后就变成了0,所以还要判别不能小于2,假如小于2则直接中止本次循环。

3、将数据输入mixup_fn生成mixup数据,然后输入model核算loss。

4、 optimizer.zero_grad() 梯度清零,把loss关于weight的导数变成0。

5、假如运用混合精度,则

  • with torch.cuda.amp.autocast(),敞开混合精度。
  • 核算loss。
  • scaler.scale(loss).backward(),梯度扩大。
  • torch.nn.utils.clip_grad_norm_,梯度裁剪,放置梯度爆炸。
  • scaler.step(optimizer) ,首先把梯度值unscale回来,假如梯度值不是inf或NaN,则调用optimizer.step()来更新权重,不然,疏忽step调用,然后保证权重不更新。
  • 更新下一次迭代的scaler。

不然,直接反向传播求梯度。torch.nn.utils.clip_grad_norm_函数履行梯度裁剪,避免梯度爆炸。

6、 torch.cuda.synchronize(),等待上面一切的操作履行完结。

7、接下来,更新loss,ACC1,ACC5的值。

等待一个epoch练习完结后,核算平均loss和平均acc

# 界说练习进程
def train(model, device, train_loader, optimizer, epoch):
    model.train()
    loss_meter = AverageMeter()
    acc1_meter = AverageMeter()
    acc5_meter = AverageMeter()
    total_num = len(train_loader.dataset)
    print(total_num, len(train_loader))
    for batch_idx, (data, target) in enumerate(train_loader):
        if len(data) % 2 != 0:
            if len(data) < 2:
                continue
            data = data[0:len(data) - 1]
            target = target[0:len(target) - 1]
            print(len(data))
        data, target = data.to(device, non_blocking=True), target.to(device, non_blocking=True)
        samples, targets = mixup_fn(data, target)
        output = model(samples)
        optimizer.zero_grad()
        if use_amp:
            with torch.cuda.amp.autocast():
                loss = criterion_train(output, targets)
            scaler.scale(loss).backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), CLIP_GRAD)
            # Unscales gradients and calls
            # or skips optimizer.step()
            scaler.step(optimizer)
            # Updates the scale for next iteration
            scaler.update()
            if use_ema and epoch%ema_epoch==0:
                ema.update()
        else:
            loss = criterion_train(output, targets)
            loss.backward()
            # torch.nn.utils.clip_grad_norm_(model.parameters(), CLIP_GRAD)
            optimizer.step()
            if use_ema and epoch%ema_epoch==0:
                ema.update()
        torch.cuda.synchronize()
        lr = optimizer.state_dict()['param_groups'][0]['lr']
        loss_meter.update(loss.item(), target.size(0))
        acc1, acc5 = accuracy(output, target, topk=(1, 5))
        loss_meter.update(loss.item(), target.size(0))
        acc1_meter.update(acc1.item(), target.size(0))
        acc5_meter.update(acc5.item(), target.size(0))
        if (batch_idx + 1) % 10 == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\tLR:{:.9f}'.format(
                epoch, (batch_idx + 1) * len(data), len(train_loader.dataset),
                       100. * (batch_idx + 1) / len(train_loader), loss.item(), lr))
    ave_loss =loss_meter.avg
    acc = acc1_meter.avg
    print('epoch:{}\tloss:{:.2f}\tacc:{:.2f}'.format(epoch, ave_loss, acc))
    return ave_loss, acc

验证函数

验证集和练习集大致类似,首要进程:

1、界说参数,test_loss测验的loss,total_num总的验证集的数量,val_list验证集的label,pred_list猜测的label。

2、在val的函数上面添加@torch.no_grad(),效果:一切核算得出的tensor的requires_grad都自动设置为False。即使一个tensor(命名为x)的requires_grad = True,在with torch.no_grad核算,由x得到的新tensor(命名为w-标量)requires_grad也为False,且grad_fn也为None,即不会对w求导。

3、运用验证集的loss函数求出验证集的loss。

4、调用accuracy函数核算ACC1和ACC5

5、更新loss_meter、acc1_meter、acc5_meter的参数。

本次epoch循环完结后,求得本次epoch的acc、loss。

假如acc比Best_ACC大,则保存模型。

# 验证进程
@torch.no_grad()
def val(model, device, test_loader):
    global Best_ACC
    model.eval()
    loss_meter = AverageMeter()
    acc1_meter = AverageMeter()
    acc5_meter = AverageMeter()
    total_num = len(test_loader.dataset)
    print(total_num, len(test_loader))
    val_list = []
    pred_list = []
    if use_ema and epoch%ema_epoch==0:
        ema.apply_shadow()
    for data, target in test_loader:
        for t in target:
            val_list.append(t.data.item())
        data, target = data.to(device,non_blocking=True), target.to(device,non_blocking=True)
        output = model(data)
        loss = criterion_val(output, target)
        _, pred = torch.max(output.data, 1)
        for p in pred:
            pred_list.append(p.data.item())
        acc1, acc5 = accuracy(output, target, topk=(1, 5))
        loss_meter.update(loss.item(), target.size(0))
        acc1_meter.update(acc1.item(), target.size(0))
        acc5_meter.update(acc5.item(), target.size(0))
    if use_ema and epoch%ema_epoch==0:
        ema.restore()
    acc = acc1_meter.avg
    print('\nVal set: Average loss: {:.4f}\tAcc1:{:.3f}%\tAcc5:{:.3f}%\n'.format(
        loss_meter.avg,  acc,  acc5_meter.avg))
    if acc > Best_ACC:
        if isinstance(model, torch.nn.DataParallel):
            torch.save(model.module, file_dir + "/" + 'model_' + str(epoch) + '_' + str(round(acc, 3)) + '.pth')
            torch.save(model.module, file_dir + '/' + 'best.pth')
        else:
            torch.save(model, file_dir + "/" + 'model_' + str(epoch) + '_' + str(round(acc, 3)) + '.pth')
            torch.save(model, file_dir + '/' + 'best.pth')
        Best_ACC = acc
    return val_list, pred_list, loss_meter.avg, acc

调用练习和验证办法

调用练习函数和验证函数的首要进程:

1、界说参数:

  • is_set_lr,是否已经设置了学习率,当epoch大于必定的次数后,会将学习率设置到必定的值,并将其置为True。

  • log_dir:记载log用的,将有用的信息保存到字典中,然后转为json保存起来。

  • train_loss_list:保存每个epoch的练习loss。

  • val_loss_list:保存每个epoch的验证loss。

  • train_acc_list:保存每个epoch的练习acc。

  • val_acc_list:保存么每个epoch的验证acc。

  • epoch_list:寄存每个epoch的值。

循环epoch

1、调用train函数,得到 train_loss, train_acc,并将别离放入train_loss_list,train_acc_list,然后存入到logdir字典中。

2、调用验证函数,得到val_list, pred_list, val_loss, val_acc。将val_loss, val_acc别离放入val_loss_list和val_acc_list中,然后存入到logdir字典中。

3、保存log。

4、打印本次的测验报告。

5、假如epoch大于600,将学习率设置为固定的1e-6。

6、绘制loss曲线和acc曲线。

     # 练习与验证
    is_set_lr = False
    log_dir = {}
    train_loss_list, val_loss_list, train_acc_list, val_acc_list, epoch_list = [], [], [], [], []
    for epoch in range(1, EPOCHS + 1):
        epoch_list.append(epoch)
        train_loss, train_acc = train(model_ft, DEVICE, train_loader, optimizer, epoch)
        train_loss_list.append(train_loss)
        train_acc_list.append(train_acc)
        log_dir['train_acc'] = train_acc_list
        log_dir['train_loss'] = train_loss_list
        val_list, pred_list, val_loss, val_acc = val(model_ft, DEVICE, test_loader)
        val_loss_list.append(val_loss)
        val_acc_list.append(val_acc)
        log_dir['val_acc'] = val_acc_list
        log_dir['val_loss'] = val_loss_list
        log_dir['best_acc'] = Best_ACC
        with open(file_dir + '/result.json', 'w', encoding='utf-8') as file:
            file.write(json.dumps(log_dir))
        print(classification_report(val_list, pred_list, target_names=dataset_train.class_to_idx))
        if epoch < 600:
            cosine_schedule.step()
        else:
            if not is_set_lr:
                for param_group in optimizer.param_groups:
                    param_group["lr"] = 1e-6
                    is_set_lr = True
        fig = plt.figure(1)
        plt.plot(epoch_list, train_loss_list, 'r-', label=u'Train Loss')
        # 显示图例
        plt.plot(epoch_list, val_loss_list, 'b-', label=u'Val Loss')
        plt.legend(["Train Loss", "Val Loss"], loc="upper right")
        plt.xlabel(u'epoch')
        plt.ylabel(u'loss')
        plt.title('Model Loss ')
        plt.savefig(file_dir + "/loss.png")
        plt.close(1)
        fig2 = plt.figure(2)
        plt.plot(epoch_list, train_acc_list, 'r-', label=u'Train Acc')
        plt.plot(epoch_list, val_acc_list, 'b-', label=u'Val Acc')
        plt.legend(["Train Acc", "Val Acc"], loc="lower right")
        plt.title("Model Acc")
        plt.ylabel("acc")
        plt.xlabel("epoch")
        plt.savefig(file_dir + "/acc.png")
        plt.close(2)

运转以及结果查看

完结上面的一切代码就可以开端运转了。点击右键,然后挑选“run train.py”即可,运转结果如下:

MaxViT实战:使用MaxViT实现图像分类任务(二)

在每个epoch测验完结之后,打印验证集的acc、recall等指标。

MaxViT实战:使用MaxViT实现图像分类任务(二)
绘制acc曲线

MaxViT实战:使用MaxViT实现图像分类任务(二)
绘制loss曲线

练习了1000个epoch,最好的成果能到达95%

测验

测验,咱们选用一种通用的方式。

测验集寄存的目录如下图:

MaxVit_demo
├─test
│  ├─1.jpg
│  ├─2.jpg
│  ├─3.jpg
│  ├ ......
└─test.py
import torch.utils.data.distributed
import torchvision.transforms as transforms
from PIL import Image
from torch.autograd import Variable
import os
classes = ('Black-grass', 'Charlock', 'Cleavers', 'Common Chickweed',
           'Common wheat', 'Fat Hen', 'Loose Silky-bent',
           'Maize', 'Scentless Mayweed', 'Shepherds Purse', 'Small-flowered Cranesbill', 'Sugar beet')
transform_test = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.51819474, 0.5250407, 0.4945761], std=[0.24228974, 0.24347611, 0.2530049])
])
DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model=torch.load('checkpoints/MaxViT/best.pth')
model.eval()
model.to(DEVICE)
path = 'test/'
testList = os.listdir(path)
for file in testList:
    img = Image.open(path + file)
    img = transform_test(img)
    img.unsqueeze_(0)
    img = Variable(img).to(DEVICE)
    out = model(img)
    # Predict
    _, pred = torch.max(out.data, 1)
    print('Image Name:{},predict:{}'.format(file, classes[pred.data.item()]))

测验的首要逻辑:

1、界说类别,这个类别的次序和练习时的类别次序对应,必定不要改动次序!!!!

2、界说transforms,transforms和验证集的transforms相同即可,别做数据增强。

3、 加载model,并将模型放在DEVICE里,

4、循环 读取图片并猜测图片的类别,在这里注意,读取图片用PIL库的Image。不要用cv2,transforms不支持。循环里面的首要逻辑:

  • 运用Image.open读取图片
  • 运用transform_test对图片做归一化和标椎化。
  • img.unsqueeze_(0) 添加一个维度,由(3,224,224)变为(1,3,224,224)
  • Variable(img).to(DEVICE):将数据放入DEVICE中。
  • model(img):履行猜测。
  • _, pred = torch.max(out.data, 1):获取猜测值的最大下角标。

运转结果:

MaxViT实战:使用MaxViT实现图像分类任务(二)

完整的代码

download.csdn.net/download/hh…