• mmlab实现图像分类



    在开始今天的讲述之前,我们还是先装一个mmcv的包,大家记得匹配自己的python版本~

    准备mmcls项目

    首先我们要下载mmclassification-master项目,然后在python中打开:
    在这里插入图片描述
    一定要以刚下载好的这个文件夹作为根目录打开,否则会出现找不到库的报错。这个项目里有很多分类模型,都包含在configs文件夹中,由于今天还是以完成花朵分类为任务,我们依然选择resnet的网络:
    在这里插入图片描述
    点开之后可以看到这个网络的具体类别,比如18层网络,50层网络等。此外,还有能适应多机多卡、不同batch的.py文件:
    在这里插入图片描述
    以resnet18_8xb16_cifar10.py为例,这就是可以适应8卡,每张卡的batch均为16,使用的数据集为cifar10的.py文件。此外,有些模型还标注了学习率衰减策略,数据增强策略等。当然如果我们的电脑是单卡也没关系,我们适当选择一个比较匹配的文件即可,所以我们选择resnet18_8xb32_in1k.py文件。首先看看里面的内容是什么:

    _base_ = [
        '../_base_/models/resnet18.py', '../_base_/datasets/imagenet_bs32.py',
        '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
    ]
    
    • 1
    • 2
    • 3
    • 4

    这个文件为我们指出了四个模块,分别为网络结构的定义,数据集定义,学习策略,常规的训练方式。不懂没关系,这四个部分就是四个路径,我们稍后需要按图索骥,挨个调整。

    基本参数及配置解读

    下面我们需要逐个进入四个模块,我们首先进入这个resnet18文件看看:

    model = dict(
        # type对应着mmcls/classifiers/image中的类名,不能写错
        # 所有的resnetxx文件都有这个type,type的值不能随意改动,必须和对应的定义严格匹配
        type='ImageClassifier',
        backbone=dict( # backbone是提取特征的依据,可选的模型包括在mmcls/models/backbones里
            type='ResNet',
            depth=18, # 层数
            num_stages=4, # 可以理解成输出的特征数,stage越靠后,输出的特征数量越少
            out_indices=(3, ), # 输出,这里设置成了只输出最后一个stage内容
            style='pytorch'), # 默认为pytorch,3*3卷积核,straight=2
        neck=dict(type='GlobalAveragePooling'), # 对特征进行整合策略,这个参数是可以改的
                                                # 对于简单的模型而言,做一个全局的平均池化(GlobalAveragePooling)即可
        head=dict( # 卷机虽然不限制输入大小,但全连接层需要限制
            type='LinearClsHead', # 全连接
            num_classes=102, # 分类数量,需要具体修改
            in_channels=512, # 根据neck特征图数量定
            loss=dict(type='CrossEntropyLoss', loss_weight=1.0), # 既要输出结果,也要计算损失
                      # 这里的type可以改成mmcls/models/losses中提供的内容(如FocalLoss)
            topk=(1, 5),
        ))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    需要强调的点我都已经放到注释里了。接下来就可以继续看imagenet_bs32.py文件了。这里主要讲的是读取数据以及对数据的一些处理,如随机剪裁、图像增强等,以及对训练集,验证集和测试集的部分设置:

    dataset_type = 'ImageNet' # 数据的格式,非固定,可按照自己的格式去做
                              # ImageNet来自于mmcls/datasets/imagenet.py
    img_norm_cfg = dict(
        mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) # 标准化
    train_pipeline = [ # 训练
        dict(type='LoadImageFromFile'),
        # 图像增强
        dict(type='RandomResizedCrop', size=224), # 随机裁剪
        dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'), # 概率旋转
        dict(type='Normalize', **img_norm_cfg), # 归一化
        dict(type='ImageToTensor', keys=['img']), # 图像数据转换格式
        dict(type='ToTensor', keys=['gt_label']), # 标签数据转换格式
        dict(type='Collect', keys=['img', 'gt_label']) # 定义返回图像和标签
    ]
    test_pipeline = [ # 测试
        # 测试不需要数据增强
        dict(type='LoadImageFromFile'),
        dict(type='Resize', size=(256, -1)), # 调整大小,避免剪裁时丢失太多信息。训练集也可以加入该操作
        dict(type='CenterCrop', crop_size=224), # 剪裁
        dict(type='Normalize', **img_norm_cfg), # 归一化
        dict(type='ImageToTensor', keys=['img']), # 图像数据转换格式
        dict(type='Collect', keys=['img']) # 测试集没有标签,需要返回分类标签
    ]
    data = dict(
        samples_per_gpu=32, # 每张卡的batch(我的电脑是单卡的),显存不够可调小。如果还不行的话可以剪裁成更小的输入图像
        workers_per_gpu=2,
        train=dict(
            type=dataset_type, # 可以自行修改
            data_prefix='data/imagenet/train', # 读取数据路径
            pipeline=train_pipeline),
        val=dict(
            type=dataset_type,
            # 两种不同方式读标签
            data_prefix='data/imagenet/val', # 以文件夹名为标签
            ann_file='data/imagenet/meta/val.txt', # 以文档为标签
    
            pipeline=test_pipeline),
        test=dict(
            # replace `data/val` with `data/test` for standard test
            type=dataset_type,
            data_prefix='data/imagenet/val',
            ann_file='data/imagenet/meta/val.txt',
            pipeline=test_pipeline))
                      # 经过interval个epoch才进行一次验证
                      # metric='accuracy'为设置评估标准
    evaluation = dict(interval=1, metric='accuracy')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    老样子,关键点已经写在注释里了。
    继续打开imagenet_bs256.py:

    # optimizer
                     # 指定优化器  学习率    动量          衰减
    optimizer = dict(type='Adam', lr=0.1, momentum=0.9, weight_decay=0.0001)
    optimizer_config = dict(grad_clip=None)
    # learning policy
    # 在迭代进行30次、60次和90次时衰减
    lr_config = dict(policy='step', step=[30, 60, 90])
    # 第一个参数最好使用默认值
    runner = dict(type='EpochBasedRunner', max_epochs=100) # 最多迭代100个epoch,可以进行修改
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    最后是default_runtime.py文件:

    # checkpoint saving
    checkpoint_config = dict(interval=20) # 间隔多少epoch保存一次,可以设置大一点
    # yapf:disable
    log_config = dict(
        interval=100,
        hooks=[
            dict(type='TextLoggerHook'),
        ])
    
    dist_params = dict(backend='nccl')
    log_level = 'INFO'
    load_from = None # 加载模型位置
    resume_from = None # 从上一个保存的epoch开始继续做训练
    workflow = [('train', 1)]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这样我们就调好了四个文件了,但是现在代码还不能跑起来,我们还需要继续做调试。

    生成完整的配置文件

    首先我们复制一下resnet18_8xb32_in1k.py完整的路径,然后打开tools文件夹,找到train文件并打开,然后在右上角依次点击:
    在这里插入图片描述
    然后将复制的路径粘贴到这里:
    在这里插入图片描述
    需要注意我们要把复制来的“\”全部改成“\”。改完之后我们就可以运行了,会有报错(就算没有报错也可以按照下面的步骤继续),但是这次运行可以生成我们需要的配置文件:
    在这里插入图片描述
    因为我们调整的内容中,生成的新文件会保存在tools\work_dirs\resnet18_8xb32_in1k路径下,点开文件夹会有一个最新生成的.py文件:
    在这里插入图片描述
    我们把它重命名并复制到resnet当中:
    在这里插入图片描述
    这个文件参考了我们上述所有的调整,并将它们整合在了一起。紧接着我们把参数再调整一下:
    在这里插入图片描述

    调整数据读取的方式

    不知道大家是否记得我们之前做花朵分类时是有两种读取文件和标签的方式的,一种是用不同文件夹储存不同分类的花朵,文件夹名就是对应分类;另一种是将所有图片都放在一个文件夹里,然后单做一个分类文本,写好图片名称和对应的分类。那么我们还是依次介绍这两种方式读取方法。

    1.文件夹名对应花朵分类

    在这里插入图片描述
    首先还是按文件夹名分类的方式,我们有训练集合验证集两个文件,两个都是102文件夹存储图像:
    在这里插入图片描述
    记好这两个路径,然后我们来修改刚刚生成好的today_resnet18_8xb32_in1k.py文件,需要改的不多,大家仔细看注释:

    model = dict(
        type='ImageClassifier',
        backbone=dict(
            type='ResNet',
            depth=18,
            num_stages=4,
            out_indices=(3, ),
            style='pytorch'),
        neck=dict(type='GlobalAveragePooling'),
        head=dict(
            type='LinearClsHead', # 全连接层
            num_classes=102,
            in_channels=512,
            loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
            topk=(1, 5)))
    dataset_type = 'ImageNet'
    img_norm_cfg = dict(
        mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
    train_pipeline = [
        dict(type='LoadImageFromFile'),
        dict(type='RandomResizedCrop', size=224), # size可以适当调整
        dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
        dict(
            type='Normalize',
            mean=[123.675, 116.28, 103.53],
            std=[58.395, 57.12, 57.375],
            to_rgb=True),
        dict(type='ImageToTensor', keys=['img']),
        dict(type='ToTensor', keys=['gt_label']),
        dict(type='Collect', keys=['img', 'gt_label'])
    ]
    test_pipeline = [
        dict(type='LoadImageFromFile'),
        dict(type='Resize', size=(256, -1)),
        dict(type='CenterCrop', crop_size=224),
        dict(
            type='Normalize',
            mean=[123.675, 116.28, 103.53],
            std=[58.395, 57.12, 57.375],
            to_rgb=True),
        dict(type='ImageToTensor', keys=['img']),
        dict(type='Collect', keys=['img'])
    ]
    data = dict(
        samples_per_gpu=32,
        workers_per_gpu=2,
        train=dict(
            type='ImageNet',
            data_prefix='../mmcls/data/flower_data/train', # 训练内容的所在路径
            pipeline=[
                dict(type='LoadImageFromFile'), # 加载数据方法,不需要改
                dict(type='RandomResizedCrop', size=224),
                dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
                dict(
                    type='Normalize',
                    mean=[123.675, 116.28, 103.53],
                    std=[58.395, 57.12, 57.375],
                    to_rgb=True),
                dict(type='ImageToTensor', keys=['img']),
                dict(type='ToTensor', keys=['gt_label']),
                dict(type='Collect', keys=['img', 'gt_label'])
            ]),
        val=dict(
            type='ImageNet',
            data_prefix='../mmcls/data/flower_data/valid',
            # ann_file就是以文件夹名作为分类标签
            # ann_file='data/imagenet/meta/val.txt', # 传入标注文件
            pipeline=[
                dict(type='LoadImageFromFile'),
                dict(type='Resize', size=(256, -1)),
                dict(type='CenterCrop', crop_size=224),
                dict(
                    type='Normalize',
                    mean=[123.675, 116.28, 103.53],
                    std=[58.395, 57.12, 57.375],
                    to_rgb=True),
                dict(type='ImageToTensor', keys=['img']),
                dict(type='Collect', keys=['img'])
            ]),
        test=dict( # 测试集暂时设置成和验证集一样
            type='ImageNet',
            data_prefix='../mmcls/data/flower_data/valid',
            # ann_file='data/imagenet/meta/val.txt',
            pipeline=[
                dict(type='LoadImageFromFile'),
                dict(type='Resize', size=(256, -1)),
                dict(type='CenterCrop', crop_size=224),
                dict(
                    type='Normalize',
                    mean=[123.675, 116.28, 103.53],
                    std=[58.395, 57.12, 57.375],
                    to_rgb=True),
                dict(type='ImageToTensor', keys=['img']),
                dict(type='Collect', keys=['img'])
            ]))
    evaluation = dict(interval=1, metric='accuracy') # 每interval个epoch做一次评估
    optimizer = dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001)
    optimizer_config = dict(grad_clip=None)
    lr_config = dict(policy='step', step=[30, 60, 90])
    runner = dict(type='EpochBasedRunner', max_epochs=100)
    checkpoint_config = dict(interval=50) # 每50个epoch保存一次
    log_config = dict(interval=100, hooks=[dict(type='TextLoggerHook')])
    dist_params = dict(backend='nccl')
    log_level = 'INFO'
    load_from = None
    resume_from = None
    workflow = [('train', 1)]
    work_dir = './work_dirs\\resnet18_8xb32_in1k' # 工作路径,可以自行拟定
                                                  # 默认为tool/work_dirs/esnet18_8xb32_in1k
    gpu_ids = [0]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    改到这里,就可以去跑train.py文件了,结果我们会在最后进行展示。

    2.文本记录图片名称和对应分类方法

    接下来我们介绍用文本记录分类的方法,只需要把存储图像的文件夹和文本文件提供好即可:
    在这里插入图片描述
    当然,我们的today_resnet18_8xb32_in1k.py文件也需要修改,修改的方法我已经在上文中的注释中提到过,这里就不在赘述了。当然,改完这个文件还是不能运行的,我们需要自己写一个数据处理的文件,放在mmcls\datasets里面,我把它写作my_filelist.py。这个文件可以参考同级目录下的imagenet.py文件。尽量不要改太多的内容,以免跑不起来:

    import numpy as np
    
    from .builder import DATASETS
    from .base_dataset import BaseDataset
    @DATASETS.register_module()
    class MyFilelist(BaseDataset): # 类名可以自己调整
        CLASS=[] # 记录分类名称
        for i in range(102):
            CLASS.append('第' + str(i) + '个类别')
        def load_annotations(self): # 方法名不要乱动
            assert isinstance(self.ann_file, str) # assert遇到False会自定义报错,如assert False,'出错了!'
    
            data_infos = [] # 新建列表,保证每一个标签要传进去
            with open(self.ann_file) as f: # 打开标注文件
                samples = [x.strip().split(' ') for x in f.readlines()] # 打开标注文件,以空格为分割符,生成一个二维列表
                for filename, gt_label in samples: # filename代表文件名,gt_label代表标签名
                    info = {'img_prefix': self.data_prefix} # 设置好文件路径的前缀
                    info['img_info'] = {'filename': filename} # 图片的名字信息
                    info['gt_label'] = np.array(gt_label, dtype=np.int64) # 图片标签信息
                    data_infos.append(info)
                return data_infos
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    写好之后,我们转到同目录的___ init __.py文件中,引用我们自己写的my_filelist:

    # Copyright (c) OpenMMLab. All rights reserved.
    from .base_dataset import BaseDataset
    from .builder import (DATASETS, PIPELINES, SAMPLERS, build_dataloader,
                          build_dataset, build_sampler)
    from .cifar import CIFAR10, CIFAR100
    from .dataset_wrappers import (ClassBalancedDataset, ConcatDataset,
                                   KFoldDataset, RepeatDataset)
    from .imagenet import ImageNet
    from .imagenet21k import ImageNet21k
    from .mnist import MNIST, FashionMNIST
    from .multi_label import MultiLabelDataset
    from .samplers import DistributedSampler, RepeatAugSampler
    from .voc import VOC
    from .my_filelist import MyFilelist # 导进自己起的文件
    
    __all__ = [
        'BaseDataset', 'ImageNet', 'CIFAR10', 'CIFAR100', 'MNIST', 'FashionMNIST',
        'VOC', 'MultiLabelDataset', 'build_dataloader', 'build_dataset',
        'DistributedSampler', 'ConcatDataset', 'RepeatDataset',
        'ClassBalancedDataset', 'DATASETS', 'PIPELINES', 'ImageNet21k', 'SAMPLERS',
        'build_sampler', 'RepeatAugSampler', 'KFoldDataset', 'MyFilelist' # 在最后加上自己新写的文件名
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    紧接着还要回到today_resnet18_8xb32_in1k.py文件中,修改好路径,然后将第53行的内容改成

            type='MyFilelist'
    
    • 1

    到这里调试也就结束啦,以文本的方式读取的代码也能跑起来了。

    训练任务

    因为上述两种方法跑起来的终端显示是一样的,我们一起来展示。运行train.py:
    在这里插入图片描述
    这里面就记录有关键的准确率信息。至于训练好的模型如何使用,就先给大家卖个关子吧,今天的内容已经很多了~

  • 相关阅读:
    RabbitMQ
    Zabbix技术分享——如何使用zabbix监控华为云RDS
    IM即时通讯开发优化提升连接成功率、速度等
    JAVA堆中的对象结构
    【算法】链表常见算法3
    python 异常机制
    最小生成树—Prim算法
    ElasticSearch(七):ES查询速度为什么那么快
    基于.Net+SWEBUI开发的开源WMS仓库管理系统
    主动信息收集
  • 原文地址:https://blog.csdn.net/weixin_54929649/article/details/127732773