携手创作,共同生长!这是我参与「日新计划 8 月更文挑战」的第6天,点击检查活动详情

练习

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

导入项目运用的库

import json
import os
import shutil
import matplotlib.pyplot as plt
import torch
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torch.utils.data.distributed
import torchvision.transforms as transforms
from apex import amp
from sklearn.metrics import classification_report
from timm.data.mixup import Mixup
from timm.loss import SoftTargetCrossEntropy
from torchtoolbox.transform import Cutout
from torchvision import datasets
from models.replknet import create_RepLKNet31B
torch.backends.cudnn.benchmark = False
import warnings
warnings.filterwarnings("ignore")
os.environ['CUDA_VISIBLE_DEVICES'] = "0,1"

设置大局参数

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

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

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

接下来,检查大局参数:

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

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

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

use_amp:是否运用混合精度。

classes:类别个数。

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

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

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

Best_ACC:记载最高ACC得分。

图画预处理与增强

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

这里留意下Resize的巨细,因为RepLKNet的输入是224224的巨细,所以要Resize为224224。

# 数据预处理7
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    Cutout(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.51819474, 0.5250407, 0.4945761], std=[0.24228974, 0.24347611, 0.2530049])
])
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])
])
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)
# 导入数据
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)
print(dataset_train.class_to_idx)
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))

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()。
  • 设置模型为RepLKNet31B,然后加载预练习模型,num_classes设置为12。假如resume为True,则加载模型接着前次练习。
  • 优化器设置为adamW。
  • 学习率调整战略挑选为余弦退火。
  • 敞开混合精度练习。
  • 检测可用显卡的数量,假如大于1,而且敞开多卡练习的情况下,则要用torch.nn.DataParallel加载模型,敞开多卡练习。
  # 实例化模型而且移动到GPU
    criterion_train = SoftTargetCrossEntropy()
    criterion_val = torch.nn.CrossEntropyLoss()
    # 设置模型
    model_ft = create_RepLKNet31B()
    model_ft.load_state_dict(torch.load('RepLKNet-31B_ImageNet-1K_224.pth'))
    numftr=model_ft.head.in_features
    model_ft.head=torch.nn.Linear(numftr,classes)
    if resume:
        model_ft = torch.load(model_path)
    model_ft.to(DEVICE)
    print(model_ft)
    # 挑选简单暴力的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:
        model_ft, optimizer = amp.initialize(model_ft, optimizer, opt_level="O1")  # 这里是“欧一”,不是“零一”
    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)

界说练习和验证函数

练习函数

练习的首要进程:

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

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

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

4、假如运用混合精度,则运用amp.scale_loss反向传达求解梯度,不然,直接反向传达求梯度。torch.nn.utils.clip_grad_norm_函数执行梯度裁剪,防止梯度爆炸。

5、 optimizer.step()更新参数。

6、接下来,获取学习率,获取loss、核算本次Batch的ACC

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

# 界说练习进程
def train(model, device, train_loader, optimizer, epoch):
    model.train()
    sum_loss = 0
    correct = 0
    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)
        loss = criterion_train(output, targets)
        optimizer.zero_grad()
        if use_amp:
            with amp.scale_loss(loss, optimizer) as scaled_loss:
                scaled_loss.backward()
            torch.nn.utils.clip_grad_norm_(amp.master_params(optimizer), CLIP_GRAD)
        else:
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), CLIP_GRAD)
        optimizer.step()
        lr = optimizer.state_dict()['param_groups'][0]['lr']
        print_loss = loss.data.item()
        sum_loss += print_loss
        _, pred = torch.max(output.data, 1)
        correct += torch.sum(pred == target)
        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 = sum_loss / len(train_loader)
    correct = correct.data.item()
    acc = correct / total_num
    print('epoch:{}\tloss:{}\tacc:{}'.format(epoch, ave_loss, acc))
    return ave_loss, acc

验证函数

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

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

2、在 with 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、求出本次batch的acc

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

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

# 验证进程
def val(model, device, test_loader):
    global Best_ACC
    model.eval()
    test_loss = 0
    correct = 0
    total_num = len(test_loader.dataset)
    print(total_num, len(test_loader))
    val_list = []
    pred_list = []
    with torch.no_grad():
        for data, target in test_loader:
            for t in target:
                val_list.append(t.data.item())
            data, target = data.to(device), target.to(device)
            output = model(data)
            loss = criterion_val(output, target)
            _, pred = torch.max(output.data, 1)
            for p in pred:
                pred_list.append(p.data.item())
            correct += torch.sum(pred == target)
            print_loss = loss.data.item()
            test_loss += print_loss
    correct = correct.data.item()
    acc = correct / total_num
    avgloss = test_loss / len(test_loader)
    print('\nVal set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        avgloss, correct, len(test_loader.dataset), 100 * acc))
    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, avgloss, 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”即可,运转成果如下:

RepLKNet实战:使用RepLKNet实现对植物幼苗的分类(非官方)(二)

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

RepLKNet实战:使用RepLKNet实现对植物幼苗的分类(非官方)(二)
制作acc曲线
RepLKNet实战:使用RepLKNet实现对植物幼苗的分类(非官方)(二)

制作loss曲线 经过练习,最好的成果能达到97%

测验

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

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

RepLKNet实战:使用RepLKNet实现对植物幼苗的分类(非官方)(二)

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((256, 256)),
    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/replknet/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):获取猜测值的最大下角标。

运转成果:

RepLKNet实战:使用RepLKNet实现对植物幼苗的分类(非官方)(二)

完好的代码

download.csdn.net/download/hh…