RCNN网络源码解读(Ⅳ) --- 训练SVM二分类模型的准备过程

news2024/12/27 4:59:11

目录

1.回忆上一讲及本讲我们要做什么

2.回顾finetune是怎么训练的(finetune.py)

3.  训练SVM二分类模型 (linear_svm.py)

3.1  load_data

3.2  custom_classifier_dataset.py

 3.3 custom_batch_sampler.py

3.4 hinge_loss


1.回忆上一讲及本讲我们要做什么

        每次我们取出来一个mini_batch= 128数量的数据进行了一个finetune训练,将选取的框体和真实的框体进行比对(IOU运算),取得了我认为它是汽车的一些图和认为他不是汽车的(局部或者不是汽车),目的是为了当我们观察到一辆汽车时保证我们把整个汽车都框下来当然汽车局部就是负例了,通过finetune训练我们希望电脑看到一个汽车整体的时候才是正例。

        我们现在要做的是:

        在使用finetune方法继承alexnet的网络模型和参数。(深度学习当中较为常用)做2分类,需要对2分类的数据集进行训练。获得了确定一张图像中是否有汽车的模型。在这个模型的基础上,进行svm二分类器的模型训练。

2.回顾finetune是怎么训练的(finetune.py)

from image_handler import show_images
import numpy as np

if __name__ == ' __main__':
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    data_loaders,data_sizes = load_data('./data/classifier_car')
    #加载alexnet神经网洛
    model = models.alexnet(pretraine = True)

    print(model)
    data_loader = data_loaders["train"]

    print("一次迭代取得所有的正负数据,如果是多个类则取得多类数据集合")
    """
    index: 323 inage_id: 200 target: 1 image.shape: (254,342,3)[xmin,ymin,xnax,ymax]: [80,39,422,293]
    """

    inputs,targets = next(data_loader.__iter__())
    print(inputs[0].size(),type(inputs[0]))
    trans = transforms.ToPILImage()
    print(type(trans(inputs[0])))
    print(targets)
    print(inputs.shape)
    titles = ["TRUE" if i.item() else "False" for i in targets[0:60]]
    images = [np.array(trans(i))for i in inputs[0:60]]
    show_images(images,titles=titles,num_cols=12)


    #
    #把alexnet变成二分类模型,在最后一行改为2分类。
    num_features = model.classifier[6].in_features
    model.classifier[6] = nn.Linear(num_features,2)
    
    print("记alexnet变成二分类模型,在最后一行改为2分类",model)
    model = model.to(device)


    criterion = nn.CrossEntroyLoss()
    optimizer = optim.SGD(model.parameters(),lr=1e-3, momentum=0.9)
    lr_scheduler = optim.lr_scheduler.StepLR(optimizer,step_size=7,gamma=0.1)
    
    best_model = train_model(data_loaders,model,criterion,optimizer,lr_scheduler,device=device
num_epachs=10)
    
    check_dir('./models')
    torch.save(best_model.state_dict(),'models/alexnet_car.pth ')

        ①指定设备

        ②读取数据,建立数据的迭代器

        ③加载alexnet神经网络

        ④alexnet变成二分类模型,在最后一行改为2分类。

        ⑤指定好参数进行训练

        ⑥保存训练模型

3.  训练SVM二分类模型 (linear_svm.py)

        我们发现有几点不同:

        ①加载模型的时候缺少了pretrain=true选项,因为我们要加载我们上一步训练好的finetune的模型。

        ②固定特征提取(注释有标注)

import time
import copy
import os
inport random
import numpy as np
inport 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 impont 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')

3.1  load_data

        我将讲解内容写在了代码注释里:

        对于random那块代码,我们用一个简单的示例进行解释:

        我们看一些random的细节 ,以正例的长度取负例的index值,大概取出来是[10,16,9,7,6,19]随机取的。

        第二行代码的意思是在负例的idx中,如果这个idx属于[10,16,9,7,6,19]中,取出它的idx对应的负例数据

        6 - 17 7 - 18 9 -20....

        第三行将剩余的负例也打印出来了。

def load_data(data_root_dir):
    transform = transforms.Compose([
        transforms.ToPILImage(),
        transforms. Resize((227,227)),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
    ])
    data_loaders={}
    data_sizes = {}
    remain_negative_list = list()

    #我们进行完for循环 得到了一个数据样本 这个样本里面正例和负例的大小相等
    for name in ['train', 'val']:
        data_dir = os.path.join(data_root_dir,name)
        
        #在后面的博客
        #这个数据又三项组成:小图片,0/1,图片信息(rect框,归属于哪张大图片)
        data_set = CustomclassifierDataset(data_dir,transform=transform)
        if name is 'train':
            """
            使用hard negative mining方式
            初始正负样本比例为1:1。由于正样本数远小于负样本,所以以正祥本数为基准,在负样本集中随机提取同样数目负样本作为初始负样本集,finetune中是32:96
            """

            #获取正例负例列表 (正向/负向框体信息 + 所属图片索引)
            positive_list = data_set.get_positives()
            negative_list = data_set.get_negatives()
        
            #finetune是取32:96的正负例样本,这里不一样!!
            #负例样本索引
            init_negative_idxs =random.sample(range(len(negative_list)),len(positive_list))

            #负例样本
            init_negative_list = [negative_list[idx] for idx in range(len(negative_list)) if idx in init_negative_idxs]
            #剩余的负例样本
            remain_negative_list = [negative_list[idx] for idx in range(len(negative_list)) if idx not in init_negative_idxs]
        
            #将数据集中的负例样本 = 正例样本
            data_set.set_negative_list(init_negative_list)

            #remain表示剩余的负例
            data_loaders['remain'] = remain_negative_list
 
        #sample是一个迭代器,含 iter_num * (32 + 96) 个样本
        sampler = CustomBatchSampler(data_set.get_positive_num(),data_set.get_negative_num(),batch_positive,batch_negative)

        #迭代器
        data_loader = DataLoader(data_set,batch_size=batch_total,sampler=sampler,num_workers=8,drop_last=True)
    
        #data_loader['train'] data_loader['val'] 
        data_loaders[name] = data_loader

        #sample是一个迭代器,含 iter_num * (32 + 96) 个样本
        data_sizes[name] = len(sampler)
    return data_loaders, data_sizes

3.2  custom_classifier_dataset.py

        我将讲解内容写在了代码注释里:

@description:分类器数据集类,可进行正负样本替换,适用于hard negative mining操作
@lhwnbnb@nefu.edu.cn
@2012/12/15

import numpy as np
import os
inport cv2
from PIL inport Image
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
import torchvision.transforms as transforms

from .util import parse_car_csv

class CustomclassifierDataset(Dataset):
    def __init__(self, root_dir,transform=None):
        #samples是图片名称
        samples = parse_car_csv(root_dir)

        jpeg_images = list()
        positive_list = list()
        negative_list = list()

        #读取单张图像
        for idx in range(len(samples)):
            #sample_name是一张图片的索引并在前面补0
            sample_name = samples[idx]
            sample_name = sample_name.zfill(6)
      
            #把sample索引对应的jepg文件都出来了                                 jpeg_images.append(cv2.imread(os.path.join(root_dir,'JPEGImages',sample_name + ".jpg”)))
            #sample这张图片对应的正例框体的索引
            positive_annotation_path = os.path.join(root_dir,'Annotations',sample_name + '_1.csv')
            positive_annotations = np.loadtxt(positive_annotation_path,dtype=np.int,delimiter=' ')

            #考虑csv文件为空或者仅包含单个标注框
            if len(positive_annotations.shape) == 1:
                #单个标注框坐标,四个数就是rect
                if positive_annotations.shape[0] == 4:
                    positive_dict = dict()
                    #positive_annotation就是四个数字,idx是大图片的索引
                    positive_dict['rect'] = positive_annotations
                    positive_dict['image_id'] = idx

                    # positive_dict[ 'image_name' ] = sample_name
                    #第几张图片的id是什么
                    positive_list.append(positive_dict)
            else:
                for positive_annotation in positive_annotations:
                    positive_dict = dict()
                    positive_dict['rect'] = positive_annotation
                    positive_dict['image_id'] = idx

                    # positive_dict['image_name'] = sample_name
                    #positivelist里面存放的是 框体 + 图像索引 的列表
                    positive_list.append(positive_dict)
            

            nagative_annotation_path = os.path.join(root_dir,'Annotations',sample_name + '_0.csv')
            nagative_annotations = np.loadtxt(positive_annotation_path,dtype=np.int,delimiter=' ')

            #考虑csv文件为空或者仅包含单个标注框
            if len(nagative_annotations.shape) == 1:
                #单个标注框坐标,四个数就是rect
                if nagative_annotations.shape[0] == 4:
                    nagative_dict = dict()
                    #nagative_annotation就是四个数字,idx是大图片的索引
                    nagative_dict['rect'] = nagative_annotations
                    nagative_dict['image_id'] = idx
                    # nagative_dict[ 'image_name' ] = sample_name
                    #第几张图片的id是什么
                    nagative_list.append(positive_dict)
            else:
                for nagative_annotation in nagative_annotations:
                    nagative_dict = dict()
                    nagative_dict['rect'] = nagative_annotation
                    nagative_dict['image_id'] = idx
                    # nagative_dict['image_name'] = sample_name
                    nagative_list.append(nagative_dict)
        self.transform = transform
        self.jpeg_images = jpeg_images
        self.positive_list = positive_list
        self.negative_list = negative_list

    def __getitem__(self,index:int):
        #定位下标所属图像
        if index < len(self.positive_list):
            #正样本
            target = 1
            #positive_dict 是 正样本的一个框体的信息(框体 + 所属图片索引)
            positive_dict = self.positive_list[index]

            xmin, ynin,xmax,ymax = positive_dict['rect']
            image_id = positive_dict['image_id']
            image = self.jpeg_images[image_id][ymin:ymax, xmin:xmax]

            #cache_dict是(框体信息 + 所属图片索引)
            cache_dict = positive_dict
        else:
            #负样本
            target = 0
            idx = index - len(self.positive_list)
            negative_dict = self.negative_list[idx]

            xmin,ymin,xmax,ynax = negative_dict['rect']
            image_id = negative_dict['image_id']
            image = self.jpeg_images[image_id][ymin:ymax, xmin:xmax]
            cache_dict = negative_dict
        if self.transform:
            image = self.transform(image)
            #返回图片,0/1,以及图片信息(框体 + 所属图片索引)

        #返回 图片,0/1,(框体 + 所属图片索引)
        return image,target,cache_dict

    #正例和负例的框体总数 
    def __len__(self) -> int:
        return len(self.positive_list) + len(self.negative_list)
    
    def get_transform(self):
        return self.transform

    def get_jpeg_images(self) ->list:
        return self.jpeg_images

    def get_positive_num(self) -> int:
        return len(self.positive_list)

    def get_negative_num(self) -> int:
        return len(self.negative_list)

    #返回(正向框体信息 + 所属图片索引)的列表
    def get_positives(self) -> list:
        return self.positive_list

    def get_negatives(self) -> list:
        return self.negative_list
    
    #替换负样本
    def set_negative_list(self, negative_list):
        self.negative_list = negative_list

 3.3 custom_batch_sampler.py

        我将讲解内容写在了代码注释里:

        和finetune里面没什么区别。。。

        这里返回num_iter个的128个数据(32+96)的迭代器。

class customBatchsampler(sampler):

    def __init__(self,num_positive,num_negative,batch_positive,batch_negative) >None:
        """
        2分类数据集
        每次批量处理,其中batch_positive个正样本,batch_negative个负样本
        @param num_positive:正样本数目
        @param num_negative:负样本数目
        @param batch_positive:单次正样本数
        @param batch_negative:难次负样本数
        """
        self.num_positive = num_positive
        self.num_negative = num_negative
        self.batch_positive = batch_positive
        self.batch_negative = batch_negative
        length = num_positive + num_negative
        self.idx_list = list(range(length))
        self.batch = batch_negative + batch_positive
        self.num_iter = length // self.batch

    def __iter__(self):
        sampler_list = list()
        for i in range(self.num_iter):
            """
            在self.idx_list的正向数据中取得32个数据
            在反面数据中获取随机96个数据作为测试数据集合
            """
            tmp = np.concatenate(
(random.sample(self.idx_list[:self.num_positive],self.batch_positive),random.sample(self.idx_list[self.num_positive:], self.batch_negative))
)
            random.shuffle(tmp)
            sampler_list.extend(tmp)
        return iter(sampler_list)

    def __len__(self)-> int:
        return self.num_iter * self.batch

    def get_num_batch(self) -> int:
        return self.num_iter

3.4 hinge_loss

        折页损失:

        具体原理请参阅我的博客:

深度学习与计算机视觉---损失函数及优化https://mp.csdn.net/mp_blog/creation/editor/128208185

def hinge_loss(outputs,labels):
    """
    折页损失计算
    :param outputs:大小为(N,num_classes)
    :param labels:大小为(N)
    :return:损失值
    面临多分类问题的时候,每个样本都经历svm计算在不同分类上的打分,其中每个样本的1oss计算方法如下
    1、针对每个样本上对不同分类的分数,选择不是该样本真实分类上的分数和该样本真实分类上的分数进行比较,如果该分数1小于真实分类上的分数,则1oss为0.
    2、反之,该样不的1oss 为该分数+1再减去该样本在真实分类上的分数,
    3、对所有的样本都按照此方法进行计算得到每个样本的LoSS,然后将它们加在一起凑成总loss值,并除以样本数以求平均。Li= Σ(0 if yi>= j+1 else 1+j-yi)(j!=yi)
    """
    num_labels = len(labels)
    corrects = outputs[range(num_labels),labels].unsqueeze(0).T

    #最大间隔
    margin = 1.0
    margins = outputs - corrects + margin
    loss = torch.sun(torch.max(margins,1)[0])/ len(labels)

    #正则化强度
    reg = 1e-3
    loss += reg * torch .sum(weight *t 2)
    return loss

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/98166.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python制作问题搜索解答器,从此学习无忧

前言 大家早好、午好、晚好吖 ❤ ~ 今天博主给大家带来一个问题搜索解答器&#xff01;&#xff01; 需要素材 以及一双慧手和一个灵活的脑子~ 效果展示 代码展示 导入模块 import requests import tkinter as tk from tkinter import ttk import webbrowserdef search(wor…

北京车牌那么难摇为什么还能那么受欢迎?

在北京生活的人来说一块北京车牌真的影响正常生活&#xff0c;特别是这两年的疫情反复...&#xff0c;面对房贷房租&#xff0c;衣食住行&#xff0c;就算外面世界再纷纷扰扰&#xff0c;也要面对...所以在北京生活没有一辆北京车牌汽车真的很麻烦。 对于在北京生活的人来说就…

【C语言刷题】PTA基础编程题目集精选

作者&#xff1a;匿名者Unit 专栏&#xff1a; 《C语言刷题》 目录题目精选6-7 统计某类完全平方数6-9 统计个位数字6-10 阶乘计算升级版6-11 求自定类型元素序列的中位数题目精选 6-7 统计某类完全平方数 我们先看一下题目要求&#xff1a; 根据题目给出的要求&#xff1a…

瑞格科技IPO被终止:曾拟募资5.6亿 江振翔三兄弟为实控人

雷递网 雷建平 12月17日浙江瑞格智能科技股份有限公司&#xff08;简称&#xff1a;“瑞格科技”&#xff09;日前IPO被终止。瑞格科技计划募资5.59亿元&#xff0c;其中&#xff0c;2.55亿元用于年产1000万套汽车配件技改项目&#xff0c;9240万元用于年产500万件智能传感器及…

css深度选择器deep

1.为什么要有deep 1.当我们给组件设置scoped的时候&#xff0c;此时我们组件的css样式只会对自己的内容生效&#xff0c;不会对子组件里面的内容生效。 <style lang"scss" scoped> .login-page {min-height: 100vh;background: url(/assets/login-bg.svg) no-r…

大脑网络的图论分析

利用图论测量大脑结构和功能网络的四个步骤: 定义网络节点——在脑电研究中&#xff0c;电极天然形成节点&#xff1b;在磁共振研究中&#xff0c;可以使用不同的脑图谱作为节点或者基于体素水平进行研究估计节点之间的连接性——结构上&#xff0c;可以由DTI计算两个脑区之间…

一文深度剖析扩散模型究竟学到了什么?

Title: <Diffusion Art or Digital Forgery? Investigating Data Replication in Diffusion Models> Paper: https://arxiv.org/pdf/2212.03860.pdf Github: Just get the point. 文章目录导读技术提升动机贡献背景图像检索与复制检测深度学习中的记忆语言模型中的记忆扩…

Linux Qt连接达梦数据库

最近因为工作需要&#xff0c;需要使用Qt连接达梦数据库&#xff0c;在Linux上比较麻烦&#xff0c;首先需要自己编译UnixODBC和Qt的QODBC库&#xff0c;其次还有各种环境配置。这里在安装好后记录一下&#xff0c;以后找起来方便。 先说下我的电脑是麒麟V10系统飞腾的CPU&…

共享SimpleDateFormat的并发问题

1、问题提出 梳理订单逻辑时发现对日期格式进行format的代码有如下写法 OneDateUtil中定义了一个全局static的SimpleDateFormat对象。SimpleDateFormat对象不是线程安全的&#xff0c;在多线程环境下容易造成数据转换和处理错误。 2、为什么SimpleDateFormat线程不安全 Sim…

[附源码]Node.js计算机毕业设计红色景点自驾游网站管理系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

MySQL~索引

7、索引 MySQL官方对索引的定义为&#xff1a;索引&#xff08;Index&#xff09;是帮助MySQL高效获取数据的数据结构。 提取句子主干&#xff0c;就可以得到索引的本质&#xff1a;索引是数据结构。 7.1、索引的分类 在一个表中&#xff0c;主键索引只能有一个&#xff0c;唯一…

微机原理与接口技术笔记(持续更新)

文章目录前言储存系统与技术材料高速储存器缓冲储存器&#xff08;Cache&#xff09;材料&#xff0c;局部性&#xff0c;访问方式Cache全相联映射Cache交换与一致性单核CPU一致性处理多核CPU的MESI协议主储存器&#xff08;内存&#xff09;主要技术指标容量带宽内存模组与内存…

机器学习100天(九):009 多项式回归理论

机器学习100天,今天讲的是:多项式回归理论! 在前两期视频我们讲解了简单线性回归理论,并解决了一个房价预测的问题,建立了一个房价与地区人口的线性关系。然而,如果数据的分布不是简单的线性关系,又该怎么做呢? 一、多项式回归 我们来看一个例子,在这个二维平面上,…

【蓝桥杯】砝码称重

3417. 砝码称重 - AcWing题库 题意&#xff1a; 思路回顾&#xff1a; 首先这道题一开始我没想用DP做&#xff0c;看到标签是入门题就没想DP qwq 其实这就是一个普通背包 一开始设计状态设计不出来&#xff0c;刚开始设的是dp[i][j]表示前i个物品能表示j种重量 显然是不行…

计算机毕业设计django基于python商品比价平台

项目介绍 随着计算机技术的发展和网络的普及。采用当前流行的B/S模式以及3层架构的设计思想通过Python技术来开发此系统的目的是建立一个配合网络环境的商品比价系统的平台,这样可以有效地解决数据商品比价系统混乱的局面。 本文首先介绍了商品比价系统的发展背景与发展现状,…

年后面试,给你提6点建议!

你好&#xff0c;我是田哥转眼年底&#xff0c;很大部分人都在观望&#xff0c;甚至已经开始着手准备明年的面试了&#xff0c;不知道屏幕前的你是如何打算的&#xff1f;从现在开始&#xff0c;到明年三月份还有两个多月的时间&#xff0c;时间不多&#xff0c;但也不少了。只…

优秀的后端应该有哪些开发习惯?

见识过各种各样的代码,优秀的、垃圾的、不堪入目的、看了想跑路的等等,所以这篇文章记录一下一个优秀的后端 Java 开发应该有哪些好的开发习惯。 拆分合理的目录结构 受传统的 MVC 模式影响,传统做法大多是几个固定的文件夹 controller、service、mapper、entity,然后无限…

CentOS7 离线部署 PostgreSQL12

CentOS7 离线部署 PostgreSQL12下载资源包部署、启动配置服务创建用户及数据库下载资源包 下载地址 https://www.postgresql.org/download/选择系统 3. 拉到最下边点击direct download 4. 选择需要的版本 5. 点击Avaliable Groups下的链接 6. 下载postgresql*、postgresql*-…

基于android的共享单车系统

效果展示&#xff1a; 需求信息&#xff1a; 客户端&#xff1a; 1&#xff1a;登录注册&#xff1a;用户可以通过自己的信息进行账号的注册 2&#xff1a;附近单车&#xff1a;显示 附近的共享单车租赁点 3&#xff1a;单车开锁&#xff1a;扫码或者输入编号开锁 4&#xff1a…

简单DP+最长上升子序列

简单DP最长上升子序列 文章目录简单DP最长上升子序列比较简单的DP[1027. 方格取数](https://www.acwing.com/problem/content/1029/)题解[275. 传纸条](https://www.acwing.com/problem/content/277/)题解最长上升子序列[AcWing1014. 登山](https://www.acwing.com/problem/con…