行人重识别 代码阅读(来自郑哲东 简单行人重识别代码到88%准确率)

来自郑哲东 简单行人重识别代码到88%准确率

  • 阅读代码
    • prepare.py
      • 数据结构
      • 部分代码
      • 一些函数
    • model.py
      • ClassBlock
      • ResNet50
    • train.py
      • 一些参数
      • 使用fp16
    • 预处理
    • 数据集迭代器
    • 训练模块

阅读代码

因为自己对代码不擅长,所以在参考这个博主的一系列博文,如Person_reID_baseline_pytorch 源码解析之 prepare.py等

prepare.py

数据结构

这部分代码主要用于重构数据集,方便之后运行代码。
在原来的数据集中加入了pytorch这个文件夹。
原来的

├── Market/
│   ├── bounding_box_test/          /* Files for testing (candidate images pool)
│   ├── bounding_box_train/         /* Files for training 
│   ├── gt_bbox/                    /* We do not use it 
│   ├── gt_query/                   /* Files for multiple query testing 
│   ├── query/                      /* Files for testing (query images)
│   ├── readme.txt

运行代码后的

├── Market/
│   ├── bounding_box_test/          /* Files for testing (candidate images pool)
│   ├── bounding_box_train/         /* Files for training 
│   ├── gt_bbox/                    /* We do not use it 
│   ├── gt_query/                   /* Files for multiple query testing 
│   ├── query/                      /* Files for testing (query images)
│   ├── readme.txt
│   ├── pytorch/
│       ├── train/                   /* train,包含train_all除val剩下的图片
│           ├── 0002
|           ├── 0007
|           ...
│       ├── val/                     /* val,包含train_all每个子文件夹的第一张图片
│       ├── train_all/               /* train+val,包含bounding_box_train 的所有图片
│       ├── query/                   /* query files,包含所有待测行人的图片
│       ├── gallery/                 /* gallery files,包含bounding_box_test 的所有图片

部分代码


#query
query_path = download_path + '/query'
query_save_path = download_path + '/pytorch/query'
if not os.path.isdir(query_save_path):
    os.mkdir(query_save_path)

for root, dirs, files in os.walk(query_path, topdown=True):
    for name in files:
        if not name[-3:]=='jpg':
            continue
        ID  = name.split('_')
        src_path = query_path + '/' + name
        dst_path = query_save_path + '/' + ID[0] 
        if not os.path.isdir(dst_path):
            os.mkdir(dst_path)
        copyfile(src_path, dst_path + '/' + name)

主要就是把原来数据集中的Market/query文件夹中的所有图像按照序号放在了新建的Market/pytorch/query/....文件夹。
行人重识别 代码阅读(来自郑哲东 简单行人重识别代码到88%准确率)_第1张图片
如以0001开头的图像放在名称为Market/pytorch/query/0001的文件夹中
行人重识别 代码阅读(来自郑哲东 简单行人重识别代码到88%准确率)_第2张图片
其他部分也大同小异,这里不再赘述。

一些函数

os.path.isdir(path)  #判断path是否为目录
os.mkdir(path)       #创建目录
os.walk(top, topdown=True, onerror=None, followlinks=False)
# top 要遍历的目录。
# topdown 遍历方式:从上到下遍历或者从下到上遍历。
# onerror 用来设置出现错误时的处理函数(该函数接受一个OSError的实例作为参数),设置为空则不作处理。
# followlinks 是否要跟随目录下的链接去继续遍历。要注意的是,os.walk不会记录已经遍历的目录,所以跟随链接遍历的话有可能一直循环调用下去。

model.py

该脚本实现了多种行人重识别模型,在看ResNet50之前,先看一下定义的分类器

ClassBlock

# Defines the new fc layer and classification layer
# |--Linear--|--bn--|--relu--|--Linear--|
class ClassBlock(nn.Module):
    def __init__(self, input_dim, class_num, droprate, relu=False, bnorm=True, linear=512, return_f = False):
        super(ClassBlock, self).__init__()
        self.return_f = return_f
        add_block = []
        if linear>0:
            add_block += [nn.Linear(input_dim, linear)]
        else:
            linear = input_dim
        if bnorm:
            add_block += [nn.BatchNorm1d(linear)]
        if relu:
            add_block += [nn.LeakyReLU(0.1)]
        if droprate>0:
            add_block += [nn.Dropout(p=droprate)]
        add_block = nn.Sequential(*add_block)
        add_block.apply(weights_init_kaiming)

        classifier = []
        classifier += [nn.Linear(linear, class_num)]
        classifier = nn.Sequential(*classifier)
        classifier.apply(weights_init_classifier)

        self.add_block = add_block
        self.classifier = classifier
    def forward(self, x):
        x = self.add_block(x)
        if self.return_f:
            f = x
            x = self.classifier(x)
            return [x,f]
        else:
            x = self.classifier(x)
            return x

ResNet50

这里使用了pytorch预训练好的模型


# Define the ResNet50-based Model
class ft_net(nn.Module):

    def __init__(self, class_num=751, droprate=0.5, stride=2, circle=False, ibn=False, linear_num=512):
    	## 下面有介绍super(ft_net, self).__init__()
        super(ft_net, self).__init__()
        model_ft = models.resnet50(pretrained=True)
        if ibn==True:
        	## 下面有介绍torch.hub.load()
            model_ft = torch.hub.load('XingangPan/IBN-Net', 'resnet50_ibn_a', pretrained=True)
        # avg pooling to global pooling
        ## 也就是平均池化改为了自适应平均池化
        if stride == 1:
            model_ft.layer4[0].downsample[0].stride = (1,1)
            model_ft.layer4[0].conv2.stride = (1,1)
        ## 是使得池化后的每个通道上的大小是一个1x1的,也就是每个通道上只有一个像素点
        model_ft.avgpool = nn.AdaptiveAvgPool2d((1,1))
        self.model = model_ft
        self.circle = circle
        ## 定义了自己的分类器
        self.classifier = ClassBlock(2048, class_num, droprate, linear=linear_num, return_f = circle)

    def forward(self, x):
    	## 卷积层1
        x = self.model.conv1(x)
        ## 归一化(Batch Normalization)常用在激活层之前
        ## 可以加快模型训练时的收敛速度,使得模型训练过程更加稳定
        ## 避免梯度爆炸或者梯度消失
        ## 起到一定的正则化作用,几乎代替了Dropout。
        x = self.model.bn1(x)
        ## 激活层(Rectified linear unit,ReLU)
        x = self.model.relu(x)
        ## 最大池化
        x = self.model.maxpool(x)
        x = self.model.layer1(x)
        x = self.model.layer2(x)
        x = self.model.layer3(x)
        x = self.model.layer4(x)
        ## 平均池化
        x = self.model.avgpool(x)
        x = x.view(x.size(0), x.size(1))
        ## 自己的分类器
        x = self.classifier(x)
        return x

1) nn.Module    #nn.Module是PyTorch体系下所有神经网络模块的基类

来自这篇博文

我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__构造函数和forward这两个方法。

  1. 一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,也可以把不具有参数的层也放在里面;
  2. 一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替
  3. forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
2) super(ft_net, self).__ init __()

来自这篇博文

super(Net, self).__ init__() 是指首先找到Net的父类(比如是类NNet),然后把类Net的对象self转换为类NNet的对象,然后“被转换”的类NNet对象调用自己的init函数,其实简单理解就是子类把父类的__init__()放到自己的__init__()当中,这样子类就有了父类的__init__()的东西。
Net类继承nn.Module,super(Net, self).__ init__() 就是对继承自父类nn.Module的属性进行初始化,而且是用nn.Module的初始化方法来初始化继承的属性。

3) def __init__(self, class_num=751, droprate=0.5, stride=2, circle=False, ibn=False, linear_num=512):

# 想知道参数都是什么意思 但是暂时不知道 先放一放
# stride 步长
4) model_ft = torch.hub.load('XingangPan/IBN-Net', 'resnet50_ibn_a', pretrained=True)

来自这篇博文

torch.hub.load(repo_or_dir, model, *args, source=‘github’, force_reload=False, verbose=True, skip_validation=False, **kwargs)

  • repo_or_dir:记得设置本地./pytorch/vision 路径
  • model 调用入口在:本地./pytorch/vision 路径下的hubconf.py文件里,因此模型名字要在hubconf.py中存在才能调用
  • source=‘local’, 记得设置本地,默认:github
  • pretrained=True #设定预训练模式,默认False,为了代码清晰,最好还是加上参数赋值.
    为了加快学习进度,训练的初期直接加载pretrain模型中预先训练好的参数
    pretrained=True 加载网络结构和预训练参数
    pretrained=False 只加载网络结构,不加载预训练参数,即不需要用预训练模型的参数来初始化。

train.py

一些参数

1) erasing_p 	# 随机擦除(Random Erasing, RE)增强

论文:Random Erasing Data Augmentation
具体就是(来自这篇)
作者提出的目的主要是模拟遮挡,从而提高模型泛化能力。如果把物体遮挡一部分后依然能够分类正确,那么肯定会迫使网络利用局部未遮挡的数据进行识别,加大了训练难度,一定程度会提高泛化能力。可以视为add noise的一种,并且与随机裁剪、随机水平翻转具有一定的互补性,综合应用他们,可以取得更好的模型表现,尤其是对噪声和遮挡具有更好的鲁棒性。
具体操作就是:随机选择一个区域,然后采用随机值进行覆盖,模拟遮挡场景。

使用fp16

使用fp16可以让运算量大大降低,会快很多,不过要安装apex(目前还没有尝试过用fp16跑)


#fp16
try:
    from apex.fp16_utils import *
    from apex import amp
    from apex.optimizers import FusedSGD
except ImportError: # will be 3.x series
    print('This is not an error. If you want to use low precision, i.e., fp16, please install the apex with cuda support (https://github.com/NVIDIA/apex) and update pytorch to 1.0')

预处理

transforms在计算机视觉工具包torchvision下,对图像进行预处理,这样可以使得之后运算的速度更快,这里加了一点注释


transform_train_list = [
        #transforms.RandomResizedCrop(size=128, scale=(0.75,1.0), ratio=(0.75,1.3333), interpolation=3), #Image.BICUBIC)
        transforms.Resize((h, w), interpolation=3), # 缩放
        transforms.Pad(10), # 填充
        transforms.RandomCrop((h, w)), # 在一个随机的位置进行裁剪
        transforms.RandomHorizontalFlip(), # 以0.5的概率水平翻转给定的PIL图像
        transforms.ToTensor(), # 图片转张量,同时归一化0-255 ---> 0-1
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 用给定的均值和标准差分别对每个通道的数据进行正则化
        ]

transform_val_list = [
        transforms.Resize(size=(h, w),interpolation=3), #Image.BICUBIC 
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]
        

数据集迭代器

来自Person_reID_baseline_pytorch 源码解析之 train.py
训练模型时,一般不会一次性把所有数据都加载到模型中。通常采用 mini_batch 的方法,按照 batchsize 的大小将一个 batch 的数据载入到模型中。pytorch 框架支持用 torch.utils.data.DataLoader 作为 dataloader 载入数据。


image_datasets = {}
image_datasets['train'] = datasets.ImageFolder(os.path.join(data_dir, 'train' + train_all),
                                          data_transforms['train'])
image_datasets['val'] = datasets.ImageFolder(os.path.join(data_dir, 'val'),
                                          data_transforms['val'])

dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=opt.batchsize,
                                             shuffle=True, num_workers=2, pin_memory=True) # 8 workers may work faster
              for x in ['train', 'val']}
              

将 image_datasets[‘train’] 和 image_datasets[‘val’] 输入 torch.utils.data.DataLoader 后,获得了两个迭代器 dataloaders[‘train’] and dataloaders[‘val’]

训练模块

# Iterate over data.
            for data in dataloaders[phase]:
                # get a batch of inputs
                inputs, labels = data
                now_batch_size,c,h,w = inputs.shape
                if now_batch_size<opt.batchsize: # skip the last batch
                    continue
                # print(inputs.shape)
                # wrap them in Variable, if gpu is used, we transform the data to cuda.
                if use_gpu:
                    inputs = Variable(inputs.cuda())
                    labels = Variable(labels.cuda())
                else:
                    inputs, labels = Variable(inputs), Variable(labels)

                # zero the parameter gradients
                optimizer.zero_grad() # 梯度初始化为零

                #-------- forward --------
                outputs = model(inputs) # 前向传播求出预测的值
                _, preds = torch.max(outputs.data, 1) # 见下面的解释
                loss = criterion(outputs, labels) # 求loss

                #-------- backward + optimize -------- 
                # only if in training phase
                if phase == 'train':
                    loss.backward() # 反向传播求梯度
                    optimizer.step() # 更新所有参数

_, preds = torch.max(outputs.data, 1)相关解释来自PyTorch系列 | _, predicted = torch.max(outputs.data, 1)的理解

关于为什么要使用 optimizer.zero_grad() 来自为什么要使用 zero_grad()?

你可能感兴趣的:(深度学习,pytorch,python)