• 售前

  • 售后

热门帖子
入门百科

ConvNeXt实战:使用ConvNeXt实现植物幼苗分类(自创,非官方)

[复制链接]
划翔的青鸟纲 显示全部楼层 发表于 2022-1-16 19:11:07 |阅读模式 打印 上一主题 下一主题
ConvNeXts 完全由标准 ConvNet 模块构建,在精确性和可扩展性方面与 Transformer 竞争,实现 87.8% ImageNet top-1 精确率,在 COCO 检测和 ADE20K 分割方面优于 Swin Transformers,同时保持标准 ConvNet 的简朴性和服从。
论文链接:https://arxiv.org/pdf/2201.03545.pdf
代码链接:https://github.com/facebookresearch/ConvNeXt
假如github不能下载,可以使用下面的毗连:
https://gitcode.net/hhhhhhhhhhwwwwwwwwww/ConvNeXt
ConvNexts的特点;


  • 使用7×7的卷积核,在VGG、ResNet等经典的CNN模子中,使用的是小卷积核,但是ConvNexts证实白大卷积和的有用性。作者实验了几种内核巨细,包罗 3、5、7、9 和 11。网络的性能从 79.9% (3×3) 进步到 80.6% (7×7),而网络的 FLOPs 大抵保持稳固, 内核巨细的利益在 7×7 处到达饱和点。
  • 使用GELU(高斯偏差线性单位)激活函数。GELUs是 dropout、zoneout、Relus的综合,GELUs对于输入乘以一个0,1构成的mask,而该mask的天生则是依概率随机的依靠于输入。实验结果要比Relus与ELUs都要好。下图是实验数据:

  • 使用LayerNorm而不是BatchNorm。
  • 倒置瓶颈。图 3 (a) 至 (b) 分析白这些设置。只管深度卷积层的 FLOPs 增长了,但由于下采样残差块的快捷 1×1 卷积层的 FLOPs 显着镌汰,这种厘革将整个网络的 FLOPs 镌汰到 4.6G。结果从 80.5% 进步到 80.6%。在 ResNet-200/Swin-B 方案中,这一步带来了更多的收益(81.9% 到 82.6%),同时也镌汰了 FLOP。

ConvNeXt残差模块

残差模块是整个模子的焦点。如下图:

代码实现:
  1. class Block(nn.Module):
  2.     r""" ConvNeXt Block. There are two equivalent implementations:
  3.     (1) DwConv -> LayerNorm (channels_first) -> 1x1 Conv -> GELU -> 1x1 Conv; all in (N, C, H, W)
  4.     (2) DwConv -> Permute to (N, H, W, C); LayerNorm (channels_last) -> Linear -> GELU -> Linear; Permute back
  5.     We use (2) as we find it slightly faster in PyTorch
  6.    
  7.     Args:
  8.         dim (int): Number of input channels.
  9.         drop_path (float): Stochastic depth rate. Default: 0.0
  10.         layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6.
  11.     """
  12.     def __init__(self, dim, drop_path=0., layer_scale_init_value=1e-6):
  13.         super().__init__()
  14.         self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim) # depthwise conv
  15.         self.norm = LayerNorm(dim, eps=1e-6)
  16.         self.pwconv1 = nn.Linear(dim, 4 * dim) # pointwise/1x1 convs, implemented with linear layers
  17.         self.act = nn.GELU()
  18.         self.pwconv2 = nn.Linear(4 * dim, dim)
  19.         self.gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)),
  20.                                     requires_grad=True) if layer_scale_init_value > 0 else None
  21.         self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
  22.     def forward(self, x):
  23.         input = x
  24.         x = self.dwconv(x)
  25.         x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C)
  26.         x = self.norm(x)
  27.         x = self.pwconv1(x)
  28.         x = self.act(x)
  29.         x = self.pwconv2(x)
  30.         if self.gamma is not None:
  31.             x = self.gamma * x
  32.         x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W)
  33.         x = input + self.drop_path(x)
  34.         return x
复制代码
数据增强Cutout和Mixup

ConvNext使用了Cutout和Mixup,为了进步结果我在我的代码中也参加这两种增强方式。官方使用timm,我没有接纳官方的,而选择用torchtoolbox。安装下令:
  1. <code>pip install torchtoolbox
复制代码
Cutout实现,在transforms中。
  1. from torchtoolbox.transform import Cutout
  2. # 数据预处理
  3. transform = transforms.Compose([
  4.     transforms.Resize((224, 224)),
  5.     Cutout(),
  6.     transforms.ToTensor(),
  7.     transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
  8. ])
复制代码
Mixup实现,在train方法中。须要导入包:from torchtoolbox.tools import mixup_data, mixup_criterion
  1.     for batch_idx, (data, target) in enumerate(train_loader):
  2.         data, target = data.to(device, non_blocking=True), target.to(device, non_blocking=True)
  3.         data, labels_a, labels_b, lam = mixup_data(data, target, alpha)
  4.         optimizer.zero_grad()
  5.         output = model(data)
  6.         loss = mixup_criterion(criterion, output, labels_a, labels_b, lam)
  7.         loss.backward()
  8.         optimizer.step()
  9.         print_loss = loss.data.item()
复制代码
项目布局

使用tree下令,打印项目布局
  1. <code>ConvNext_demo
  2. ├─data
  3. │  ├─test
  4. │  └─train
  5. │      ├─Black-grass
  6. │      ├─Charlock
  7. │      ├─Cleavers
  8. │      ├─Common Chickweed
  9. │      ├─Common wheat
  10. │      ├─Fat Hen
  11. │      ├─Loose Silky-bent
  12. │      ├─Maize
  13. │      ├─Scentless Mayweed
  14. │      ├─Shepherds Purse
  15. │      ├─Small-flowered Cranesbill
  16. │      └─Sugar beet
  17. ├─dataset
  18. │  ├─ __init__.py
  19. │  └─ dataset.py
  20. ├─Model
  21. │    └─convnext.py
  22. ├─ test1.py
  23. ├─ test2.py
  24. └─ train_connext.py
复制代码
数据集

数据集选用植物幼苗分类,统共12类。数据集毗连如下:
链接:https://pan.baidu.com/s/1TOLSNj9JE4-MFhU0Yv8TNQ
提取码:syng
在工程的根目次新建data文件夹,获取数据集后,将trian和test解压放到data文件夹下面,如下图:

导入模子文件

从官方的链接中找到convnext.py文件,将其放入Model文件夹中。如图:

安装库,并导入须要的库

模子用到了timm库,假如没有须要安装,实验下令:
  1. <code>pip install timm
复制代码
新建train_connext.py文件,导入所须要的包:
  1. import torch.optim as optim
  2. import torch
  3. import torch.nn as nn
  4. import torch.nn.parallel
  5. import torch.utils.data
  6. import torch.utils.data.distributed
  7. import torchvision.transforms as transforms
  8. from dataset.dataset import SeedlingData
  9. from torch.autograd import Variable
  10. from Model.convnext import convnext_tiny
  11. from torchtoolbox.tools import mixup_data, mixup_criterion
  12. from torchtoolbox.transform import Cutout
复制代码
设置全局参数

设置使用GPU,设置学习率、BatchSize、epoch等参数。
  1. # 设置全局参数
  2. modellr = 1e-4
  3. BATCH_SIZE = 8
  4. EPOCHS = 300
  5. DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
复制代码
数据预处理处罚

数据处理处罚比力简朴,没有做复杂的实验,有爱好的可以参加一些处理处罚。
  1. # 数据预处理
  2. transform = transforms.Compose([
  3.     transforms.Resize((224, 224)),
  4.     Cutout(),
  5.     transforms.ToTensor(),
  6.     transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
  7. ])
  8. transform_test = transforms.Compose([
  9.     transforms.Resize((224, 224)),
  10.     transforms.ToTensor(),
  11.     transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
  12. ])
复制代码
数据读取
然后我们在dataset文件夹下面新建 init.py和dataset.py,在mydatasets.py文件夹写入下面的代码:
说一下代码的焦点逻辑。
第一步 创建字典,界说种别对应的ID,用数字取代种别。
第二步 在__init__内里编写获取图片路径的方法。测试集只有一层路径直接读取,练习集在train文件夹下面是种别文件夹,先获取到种别,再获取到详细的图片路径。然后使用sklearn中切分数据集的方法,按照7:3的比例切分练习集和验证集。
第三步 在__getitem__方法中界说读取单个图片和种别的方法,由于图像中有位深度32位的,以是我在读取图像的时间做了转换。
代码如下:
  1. # coding:utf8
  2. import os
  3. from PIL import Image
  4. from torch.utils import data
  5. from torchvision import transforms as T
  6. from sklearn.model_selection import train_test_split
  7. Labels = {'Black-grass': 0, 'Charlock': 1, 'Cleavers': 2, 'Common Chickweed': 3,
  8.           'Common wheat': 4, 'Fat Hen': 5, 'Loose Silky-bent': 6, 'Maize': 7, 'Scentless Mayweed': 8,
  9.           'Shepherds Purse': 9, 'Small-flowered Cranesbill': 10, 'Sugar beet': 11}
  10. class SeedlingData(data.Dataset):
  11.     def __init__(self, root, transforms=None, train=True, test=False):
  12.         """
  13.         主要目标: 获取所有图片的地址,并根据训练,验证,测试划分数据
  14.         """
  15.         self.test = test
  16.         self.transforms = transforms
  17.         if self.test:
  18.             imgs = [os.path.join(root, img) for img in os.listdir(root)]
  19.             self.imgs = imgs
  20.         else:
  21.             imgs_labels = [os.path.join(root, img) for img in os.listdir(root)]
  22.             imgs = []
  23.             for imglable in imgs_labels:
  24.                 for imgname in os.listdir(imglable):
  25.                     imgpath = os.path.join(imglable, imgname)
  26.                     imgs.append(imgpath)
  27.             trainval_files, val_files = train_test_split(imgs, test_size=0.3, random_state=42)
  28.             if train:
  29.                 self.imgs = trainval_files
  30.             else:
  31.                 self.imgs = val_files
  32.     def __getitem__(self, index):
  33.         """
  34.         一次返回一张图片的数据
  35.         """
  36.         img_path = self.imgs[index]
  37.         img_path = img_path.replace("\", '/')
  38.         if self.test:
  39.             label = -1
  40.         else:
  41.             labelname = img_path.split('/')[-2]
  42.             label = Labels[labelname]
  43.         data = Image.open(img_path).convert('RGB')
  44.         data = self.transforms(data)
  45.         return data, label
  46.     def __len__(self):
  47.         return len(self.imgs)
复制代码
然后我们在train.py调用SeedlingData读取数据 ,记取导入刚才写的dataset.py(from mydatasets import SeedlingData)
  1. # 读取数据
  2. dataset_train = SeedlingData('data/train', transforms=transform, train=True)
  3. dataset_test = SeedlingData("data/train", transforms=transform_test, train=False)
  4. # 导入数据
  5. train_loader = torch.utils.data.DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
  6. test_loader = torch.utils.data.DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=False)
复制代码
设置模子

设置loss函数为nn.CrossEntropyLoss()。


  • 设置模子为coatnet_0,修改末了一层全毗连输出改为12(数据集的种别)。
  • 优化器设置为adam。
  • 学习率调解战略改为余弦退火
    1. # 实例化模型并且移动到GPU
    2. criterion = nn.CrossEntropyLoss()
    3. #criterion = SoftTargetCrossEntropy()
    4. model_ft = convnext_tiny(pretrained=True)
    5. num_ftrs = model_ft.head.in_features
    6. model_ft.fc = nn.Linear(num_ftrs, 12)
    7. model_ft.to(DEVICE)
    8. # 选择简单暴力的Adam优化器,学习率调低
    9. optimizer = optim.Adam(model_ft.parameters(), lr=modellr)
    10. cosine_schedule = optim.lr_scheduler.CosineAnnealingLR(optimizer=optimizer,T_max=20,eta_min=1e-9)
    复制代码
界说练习和验证函数

alpha=0.2 Mixup所需的参数。
  1. # 界说练习过程alpha=0.2def train(model, device, train_loader, optimizer, epoch):    model.train()    sum_loss = 0    total_num = len(train_loader.dataset)    print(total_num, len(train_loader))    for batch_idx, (data, target) in enumerate(train_loader):
  2.         data, target = data.to(device, non_blocking=True), target.to(device, non_blocking=True)
  3.         data, labels_a, labels_b, lam = mixup_data(data, target, alpha)
  4.         optimizer.zero_grad()
  5.         output = model(data)
  6.         loss = mixup_criterion(criterion, output, labels_a, labels_b, lam)
  7.         loss.backward()
  8.         optimizer.step()
  9.         print_loss = loss.data.item()        sum_loss += print_loss        if (batch_idx + 1) % 10 == 0:            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(                epoch, (batch_idx + 1) * len(data), len(train_loader.dataset),                       100. * (batch_idx + 1) / len(train_loader), loss.item()))    ave_loss = sum_loss / len(train_loader)    print('epoch:{},loss:{}'.format(epoch, ave_loss))ACC=0# 验证过程def val(model, device, test_loader):    global ACC    model.eval()    test_loss = 0    correct = 0    total_num = len(test_loader.dataset)    print(total_num, len(test_loader))    with torch.no_grad():        for data, target in test_loader:            data, target = Variable(data).to(device), Variable(target).to(device)            output = model(data)            loss = criterion(output, target)            _, pred = torch.max(output.data, 1)            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 > ACC:            torch.save(model_ft, 'model_' + str(epoch) + '_' + str(round(acc, 3)) + '.pth')            ACC = acc# 练习for epoch in range(1, EPOCHS + 1):    train(model_ft, DEVICE, train_loader, optimizer, epoch)    cosine_schedule.step()    val(model_ft, DEVICE, test_loader)
复制代码
然后就可以开始练习了

练习10个epoch就能得到不错的结果:

测试

第一种写法

测试集存放的目次如下图:

第一步 界说种别,这个种别的序次和练习时的种别序次对应,肯定不要改变序次!!!!
  1. classes = ('Black-grass', 'Charlock', 'Cleavers', 'Common Chickweed',
  2.            'Common wheat', 'Fat Hen', 'Loose Silky-bent',
  3.            'Maize', 'Scentless Mayweed', 'Shepherds Purse', 'Small-flowered Cranesbill', 'Sugar beet')
复制代码
第二步 界说transforms,transforms和验证集的transforms一样即可,别做数据增强。
  1. transform_test = transforms.Compose([
  2.          transforms.Resize((224, 224)),
  3.         transforms.ToTensor(),
  4.         transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
  5. ])
复制代码
第三步 加载model,并将模子放在DEVICE里。
  1. <code>DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  2. model = torch.load("model_8_0.971.pth")
  3. model.eval()
  4. model.to(DEVICE)
复制代码
第四步 读取图片并推测图片的种别,在这里注意,读取图片用PIL库的Image。不要用cv2,transforms不支持。
  1. path = 'data/test/'
  2. testList = os.listdir(path)
  3. for file in testList:
  4.     img = Image.open(path + file)
  5.     img = transform_test(img)
  6.     img.unsqueeze_(0)
  7.     img = Variable(img).to(DEVICE)
  8.     out = model(img)
  9.     # Predict
  10.     _, pred = torch.max(out.data, 1)
  11.     print('Image Name:{},predict:{}'.format(file, classes[pred.data.item()]))
复制代码
测试完备代码:
  1. import torch.utils.data.distributedimport torchvision.transforms as transformsfrom PIL import Imagefrom torch.autograd import Variableimport osclasses = ('Black-grass', 'Charlock', 'Cleavers', 'Common Chickweed',
  2.            'Common wheat', 'Fat Hen', 'Loose Silky-bent',
  3.            'Maize', 'Scentless Mayweed', 'Shepherds Purse', 'Small-flowered Cranesbill', 'Sugar beet')transform_test = transforms.Compose([    transforms.Resize((224, 224)),    transforms.ToTensor(),    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])<code>DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  4. model = torch.load("model_8_0.971.pth")
  5. model.eval()
  6. model.to(DEVICE)path = 'data/test/'
  7. testList = os.listdir(path)
  8. for file in testList:
  9.     img = Image.open(path + file)
  10.     img = transform_test(img)
  11.     img.unsqueeze_(0)
  12.     img = Variable(img).to(DEVICE)
  13.     out = model(img)
  14.     # Predict
  15.     _, pred = torch.max(out.data, 1)
  16.     print('Image Name:{},predict:{}'.format(file, classes[pred.data.item()]))
复制代码
运行结果:

第二种写法

第二种,使用自界说的Dataset读取图片。前三步同上,差异紧张在第四步。读取数据的时间,使用Dataset的SeedlingData读取。
  1. dataset_test =SeedlingData('data/test/', transform_test,test=True)
  2. print(len(dataset_test))
  3. # 对应文件夹的label
  4. for index in range(len(dataset_test)):
  5.     item = dataset_test[index]
  6.     img, label = item
  7.     img.unsqueeze_(0)
  8.     data = Variable(img).to(DEVICE)
  9.     output = model(data)
  10.     _, pred = torch.max(output.data, 1)
  11.     print('Image Name:{},predict:{}'.format(dataset_test.imgs[index], classes[pred.data.item()]))
  12.     index += 1
复制代码
运行结果:

完备代码:
https://download.caogenba.net/download/hhhhhhhhhhwwwwwwwwww/75920884

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作