RCNN网络源码复现(Ⅴ) --- 训练SVM二分类模型的训练过程

目录

1.我们来回顾SVM的训练过程

2.开始训练

3.难分辨负样本挖掘 (custom_hard_negative_mining_dataset.py)

3.1 get_hard_negatives 

3.2 add_hard_negatives 

3.3 总结负样本挖掘 


1.我们来回顾SVM的训练过程

import time
import copy
import os
import random
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
from torchvision.models import alexnet
from utils.data.custom_classifier_dataset import CustomClassifierDataset
from utils.data.custom_hard_negative_mining_dataset import CustomHardNegativeNiningDatasetfrom utils.data.custom_batch_sampler 
import customBatchSampler
from utils.util import check_dir
from utils.util import save_model
 
 
if __name__ ==  '__main__':
    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
 
    #dataloadr 有train val remain
    #dataloader是一个样本含有128(32+96)个框体的迭代器,这个迭代器含元素个数为data_size
    data_loaders,data_sizes = load_data('./data/classifier_car')
    #加载CNN模型
    model_path = "./models/alexnet_car.pth'
    model = alexnet()
    #指定二分类
    num_classes = 2
    #将Alexnet的最后一层改成一个线性层(第六层):因为我们之前训练好的
    #Alexnet最后一层是两个输出的,如果这里不改改网络结构的话那么我们模型加载不出来
    num_features = model.classifier[6].in_features
    model.classifier[6] =nn.Linear(num_features,nun_classes)
    #将finetune模型训练好的数据加载进去
    model.load_state_dict(torch.load(model_path))
    #进入估算模式
    model.eval()
    #固定特征提取:迁移学习 不取梯度了
    for param in model.parameters():
        param.requires_grad = False
    #创建SVM分类器:再将第六层设置为一个二分类
    #那么最后一层的param.requires_grad = True
    model.classifier[6] = nn.Linear(num_features,nun_classes)
    #print(model)
    model = model.to(device)
    ##查看各层的训练情况:最后一次required_grad = true
    #for param in model.parameters():
    #    print(param,param.requires_grad)
    for name,param in model.named_parameters(): #查看可优化的参数有哪些
        print(name,param.size(), param.requires.grad)
    criterion = hinge_loss
    #由于初始训练集数量很少,所以降低学习率
    optimizer = optim.SGD(model.parameters(),lr=1e-4,momentum=0.9)
    #共训练10轮,每隔4论减少一次学习率
    lr_schduler = optim.lr_scheduler.stepLR(optimizer,step_size=4,gamma=0.1)
    best_model = train_model(data_loaders,model,criterion,optimizer,1r_schduler,num_epochs=10,device=device)
    #保存最好的模型参数
    save_model(best_model,'models/best_linear_svm_alexnet_car.pth')

        上篇博客我们说到了训练之前的准备工作,即准备好数据,将finetune模型训练好的数据取出,接下来我们看看如何进行SVM训练以及难分类样本挖掘的过程吧!

2.开始训练

def train_model(data_loaders,model,criterionion,optimizer,lr_scheduler,num_epochs=25,device=None):
    since = time.time()

    #model.state_dict()是上一步被加载进去的数据,这里是做了迁移学习
    best_model_weights = copy.deepcopy(model.state_dict())
    best_acc = 0.0

    #这里指定轮数 = 25
    for epoch in range(num_epochs):
        #输出训练轮次
        print('Epoch {}/{}'.format(epoch,num_epochs - 1))
        print('-' * 10)
 

        #Each epoch has a training and validation phase
        for phase in ['train','val']:
            if phase == 'train ':
                model.train() # set model to training mode
            else:
                model.eval() # Set model to evaluate mode

            running_loss = 0.0
            running_corrects = 0

            #输出正负样本数
            #train - positive_num = 621  negative_num = 621 datasize = 1152
            #val   - positive_num = 617  negative_num =312808 datasize = 331344
            data_set = data_loaders[phase].dataset
            print('{} -positive_num:{} - negative_num:{}- data size: {}'.format(
phase,data_set.get_positive_num(),data_set.get_negative_num(),data_sizes[phase]))
            
            #Iterate over data.
            for inputs,labels,cache_dicts in data_loader[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)
                
                #zero the parameter gradients
                optimizer.zero_grad()
                
                #forward
                #track history if only in train
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    # print(outputs.shape)
                    #每一个样例都有两个分数,针对一项中取出最大分数的索引值
                    _,preds = torch.max(outputs,1)
                    loss = criterion(outputs,labels)

                    #backward + optimize only if in training phase
                    if phase == 'train':
        l               loss.backward()
                        optimizer.step()

                # statistics                       128
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

                print("统计正确loss和正确数")
                print("running_loss",running_loss)
                print("preds == labels.data",preds == labels.data)
                print("1",inputs.size(0))
                print("2",labels.data)
                print("3",running_corrects)
                
            if phase == 'train':
                lr_scheduler.step()

            #data_sizes[phase] phase = train/val 表示train或val样本集的总量
            epoch_loss = running_loss / data_sizes[phase]
            epoch_acc = running_corrects.double()/data_sizes[phase]
        
            print('{} Loss: {.4f} Acc: {:.4f}'.format(phase,epoch_loss,epoch_acc))
    
            #拷贝训练阶段最好的模型
            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_weights = copy.deepcopy(model.state_dict())

        #每一轮训练完成后,测试剩余负样本集,进行hard negative mining
        #就是多找一些hard negative加入负样本集,进行训练,这样会比easy negative组成的负样本集效果更好
        train_dataset = data_loaders['train'].dataset      #1200多
        remain_negative_list = data_loaders['remain']      #三十多万
        jpeg_images = train_dataset.get_jpeg_images()      #取出600多张图片
        transfonm = train_dataset.get_transform()          #拿到图片变换方式
        
        #将grad锁住
        with torch.set_grad_enabled(False):
            #传入没有使用过的负样本集 图片 图片变换方式
            remain_dataset =CustomHandNegativeMiningDataset(remain_negative_list,jpeg_images,transform=transform)

            #batch_total = 128
            remain_data_loader = DataLoader(remain_dataset,batch_size=batch_total,num_workers=8,drop_last=True)
        
            #获取训练数据集的负样本集 621
            negative_list = train_dataset.get_negatives()
            #记录后续增加的负样本
            add_negative_list = data_loaders.get('add_negative',[])

        
            running_corrects = 0
            # Itenate over data.
            # 一次传进来128个负样本
            for inputs,labels, cache_dicts in remain_data_loader:
                inputs = inputs.to(device)
                labels = labels.to(device)

                #zero the paraneter gradients
                optimizer.zero_grad()

                outputs = model(inputs) 
                print(outputs.shape)
                _,preds = torch.max(outputs,1)
        
                running_corrects += torch.sum(preds == labels.data)

                #hard_negative_list是从负样本集挖掘出来的难以辨别的样本格式为:{框体,原图idx}
                #区分出难分辨不难分辨样本
                hard_negative_list,easy_negative_list =get_hard_negatives(preds.cpu().numpy(),cache_dicts)

            #negative_list是原有的从dataset取到的621张
            #add_negative_list 是 0
            add_hard_negatives(hard_negative_list,negative_list,add_negative_list)

            remain_acc = running_corrects.double() / len(remain_negative_list)
            print('remain negative size:{},acc:{.4}'.fonmat(len(remain_negative_list),remain_acc)

            #经过这步之后negative_list发生了变化,项数变多
            #add_hard_negatives的项目数也便多

            #训练完成后,重置负样本,进行hard negatives mining                   
            train_dataset.set_negative_list(negative_list)

            #重新挖掘了128个数据
            tmp_sampler = CustomBatchSampler(train_dataset.get_positive_num(),train_    dataset.get._negative_num(),batch_positive,batch_negative)
        
            data_loaders['train'] = DataLoader(train_dataset,batchsize=batch_total,sampler=tmp_sampler,
nun_workers=8,drop_last=True)
            data_loaders['add_negative'] = add_negative_list

            #重置数据集大小
            data_sizes['train'] = len(tmp_sampler)
        #每训练一轮就保存
        save_model(model,'models/linear_sva_alexnet_car_%d.pth' % epoch)

    time_elapsed = time.time - since
    print('Training complete in in {:.0f}m {:.0f}s'.format(
            time_elapsed // 60,time_elapsed % 60))
    print('Best val Acc:{:4f}'.format(best_acc))

    model.load_state_dict(best_model_weights)
    return model

        pred相当于这部分内容:比如0分数高就是假值,1分数高就是真值

pred -- 1是指每个样例都有两个分数,每一个分数代表在真值和非真值上的评分,在一项里面我取到哪项最大记为1,因此这里的值告诉我们分数最大的索引值是什么

        preds == labels.data 是真实值和训练值的差异,sum是记一个总值的意思。

        我们打印一下:

RCNN网络源码复现(Ⅴ) --- 训练SVM二分类模型的训练过程_第1张图片 preds == labels.data
RCNN网络源码复现(Ⅴ) --- 训练SVM二分类模型的训练过程_第2张图片 测试 : 详情看代码

3.难分辨负样本挖掘 (custom_hard_negative_mining_dataset.py)

        我们在进行svm二分类器的模型训练时,首先先取得和正例相同的负例作为我们的训练样本。本例中正样本621负样本621。
        进行第每轮训练并计算出准确率和loss,根据验证集准确率表现,同时判断是否可以存储为最好的模型参数。
        然后在每一轮训练完成后,测试剩余负样本集,进行hard negative mining,就是多找一些hard negative加入负样本集,进行训练,这样会比easy negative组成的负样本集效果更好。

@样本挖掘

class CustomHardNegativeMiningDataset(Dataset):

    def __init__ (self,negative_list,jpeg_images,transform=None):
        self.negative_list = negative_list
        self.jpeg_images = jpeg_images
        self.transform = transform
    
    def __getitem__(self,index:int):
        target = 0

        negative_dict = self.negative_list[index]
        xmin, ymin,xmax, ymax = negative_dict['rect']
        image_id = negative_dict['image_id']

        image = self.jpeg_inages[image_id][ymin:ymax,xmin:xmax]
        if self.transform:
            image = self.transform(image)

        return image,target,negative_dict
    
    def __len__(self) -> int:
        return len(self.negarive.list)

3.1 get_hard_negatives 

        调用函数传进来的是preds.cpu().numpy(),cache_dicts。即一个分数值的tensor和该张图片以及框体的索引。

def get_hard_negatives(preds,cache_dicts):
    print("获取本来是负例,却判斯为正例,和轻易就被判斯为负例的样本")
    print(len(preds), len(cache_dicts))
    print(preds)
    print(cache_dicts)

    #掩码。里面为true和false的tensor
    fp_mask = preds == 1   #原本都是=0的,如果例子等于1的情况(错误分为正例)
    tn_mask = preds == 0   
    print(fp_mask)
    print(tn_mask)

    #获取难分辨的掩码值所获取的rect对象
    #cache_dicts['rect'] 是负例的窗体
    fp_rects = cache_dicts['rect'][fp_mask].numpy()

    #获取框体在哪张图里面
    fp_image_ids = cache_dicts['image_id'][fp_mask].numpy()

    print("fp_nects"",fp_rects)
    print("fp_image_ids", fp_image_ids)

    tn_rects = cache_dicts['rect'][tn_mask].numpy()
    tn_image_ids = cache_dicts[’image_id'][tn_mask].numpy()

    print("tn_nects", tn_rects)
    print("tn_image_ids", tn_image_ids)

    hand_negative_list = [{'rect': fp_rects[idx],'image_id': fp_image _ids[idx]} for idx in range(len(fp_rects))]
    easy.negatie_list = [{'rect': tn_rects[idx],'image_id': tn_image_ids[idx]} for idx in range(len(tn_rects))]

    print("hard_negative_list", hard_negative_list)
    print("easy_negatie_list", easy_negatie_list)

    return hard_negative_list, easy_negative_list

3.2 add_hard_negatives 

        参数:

        hard_negative_list        在1轮中抽取的128个负例中的难分辨样本,形式是(rect,imageidx)

        negative_list                 负样本数据集(621个框体)

        add_negative_list         记录后续增加的负样本

        扩充negative_list并记录add_negative_list。         

def add_hard_negatives(hard_negative_list,negative_list,add_negative_list):
    print("1hard_negative_list", hard_negative_list)
    print("2negative_list", len(negative_list))
    print("3add_negative_list", len(add_negative_list))


    for item in hard_negative_list:
        #如果add_negative_list长度为0
        if len(add_negative_list) == 0:
            #第一次添加负样本的情况,将数据加进去不用检查是否重复添加
            negative_list.append(item)
            add_negative_list.append(list(item['rect']))
        if list(item['rect"]) not in add_negative_list:
            negative_list.append(item)
            add_negative_list.append(list(item[ 'rect']))

        print("4hard_negative_list",hard_negative_list)
        print("5negative_list",len(negative_list))
        print("6add_negative_list", len(add_negative_list))

3.3 总结负样本挖掘 

        我们刚开始的positive_list中包含621个框体,negative_list中包含621个框体。

        在进行一次迭代时,我们从上述数据取得正例32 + 负例96 作为一次训练数据,当我们完成一次训练时进行负样本挖掘,即添加一部分数据到negative_list中,这时negative_list就不只有621个数据了,然后下一轮迭代我们继续从上述数据取得正例32 + 负例96 作为一次训练数据........。

你可能感兴趣的:(RCNN网络源码复现,计算机视觉与深度学习,深度学习,人工智能,计算机视觉,cnn)