【DenseFusion代码详解】训练过程train.py

DenseFusion系列代码全讲解目录:【DenseFusion系列目录】代码全讲解+可视化+计算评估指标_Panpanpan!的博客-CSDN博客

这些内容均为个人学习记录,欢迎大家提出错误一起讨论一起学习!


train过程包括train和test,在每一次迭代之后都对本轮迭代的模型进行test。代码位置tools/train.py

首先是一些超参数的设置:

parser = argparse.ArgumentParser()
parser.add_argument('--dataset', type=str, default = 'ycb', help='ycb or linemod')
parser.add_argument('--dataset_root', type=str, default = '', help='dataset root dir (''YCB_Video_Dataset'' or ''Linemod_preprocessed'')')
parser.add_argument('--batch_size', type=int, default = 8, help='batch size')
parser.add_argument('--workers', type=int, default = 10, help='number of data loading workers')
parser.add_argument('--lr', default=0.0001, help='learning rate')
parser.add_argument('--lr_rate', default=0.3, help='learning rate decay rate')
parser.add_argument('--w', default=0.015, help='learning rate')
parser.add_argument('--w_rate', default=0.3, help='learning rate decay rate')
parser.add_argument('--decay_margin', default=0.016, help='margin to decay lr & w')
parser.add_argument('--refine_margin', default=0.013, help='margin to start the training of iterative refinement')
parser.add_argument('--noise_trans', default=0.03, help='range of the random noise of translation added to the training data')
parser.add_argument('--iteration', type=int, default = 2, help='number of refinement iterations')
parser.add_argument('--nepoch', type=int, default=500, help='max number of epochs to train')
parser.add_argument('--resume_posenet', type=str, default = '',  help='resume PoseNet model')
parser.add_argument('--resume_refinenet', type=str, default = '',  help='resume PoseRefineNet model')
parser.add_argument('--start_epoch', type=int, default = 1, help='which epoch to start')
opt = parser.parse_args()

argparse 模块是一种命令行接口,也就是在.sh文件或者命令行输入相应的参数,赋予opt中相应变量的具体值。上述变量分别的含义如下:

--dataset:数据集,选择YCB或者LineMOD,默认为YCB

--dataset_root:数据集的路径

--batch_size:批量大小,但这里训练的时候都是1,可能是因为每次分割出的图片大小都不一样,只能一个一个训练

--workers:读取数据的进程数量,PyTorch的 DataLoader 允许使用多进程来加速数据读取

--lr:学习率

--lr_rate:学习率衰减率

--w:平衡超参数

--w_rate:权重衰减率

--decay_margin:衰减阈值

--refine_margin:开始迭代自优化refine的阈值

--noise_trans:添加到训练数据中随机噪声的范围

--iteration:迭代自优化的次数

--nepoch:最大训练周期

--resume_posenet:之前训练已经保存的posenet模型

--resume_refinenet:之前训练已经保存的refinenet模型

--start_epoch:开始训练的epoch

下面是main()函数中的内容。

首先设置随机数种子,用于参数初始化。

def main():
    opt.manualSeed = random.randint(1, 10000)
    random.seed(opt.manualSeed)
    torch.manual_seed(opt.manualSeed)

下面定义数据集,包括物体类别数,随机选取的点云数、保存路径等:

    #选择数据集
    if opt.dataset == 'ycb':
        opt.num_objects = 21 #数据集中物体的类别数
        opt.num_points = 1000 #随机筛选点云的点数
        opt.outf = 'trained_models/ycb' #保存训练模型的路径
        opt.log_dir = 'experiments/logs/ycb' #保存log文件的路径
        opt.repeat_epoch = 1 #number of repeat times for one epoch training
    elif opt.dataset == 'linemod':
        opt.num_objects = 13
        opt.num_points = 500
        opt.outf = 'trained_models/linemod'
        opt.log_dir = 'experiments/logs/linemod'
        opt.repeat_epoch = 20
    else:
        print('Unknown dataset')
        return

这里区别两个数据集,物体类别数分别为21和13,输入点云的点数分别为1000和500,分别将model和log保存在各自的文件夹下面。

    #选择网络
    estimator = PoseNet(num_points = opt.num_points, num_obj = opt.num_objects)
    estimator.cuda()
    refiner = PoseRefineNet(num_points = opt.num_points, num_obj = opt.num_objects)
    refiner.cuda()

estimator为PoseNet网络,即用于预测姿态的主干网络,refiner为PoseRefineNet网络,用于后续迭代自优化。

    #是否加载前面训练的posenet模型
    if opt.resume_posenet != '':
        estimator.load_state_dict(torch.load('{0}/{1}'.format(opt.outf, opt.resume_posenet)))
    #是否加载前面训练的refinenet模型
    if opt.resume_refinenet != '':
        refiner.load_state_dict(torch.load('{0}/{1}'.format(opt.outf, opt.resume_refinenet)))
        opt.refine_start = True #开始refine过程
        opt.decay_start = True #开始衰减
        opt.lr *= opt.lr_rate #学习率衰减
        opt.w *= opt.w_rate #权重衰减
        opt.batch_size = int(opt.batch_size / opt.iteration)
        optimizer = optim.Adam(refiner.parameters(), lr=opt.lr) #优化器
    else:
        opt.refine_start = False #还没开始refine过程
        opt.decay_start = False #还没开始衰减
        optimizer = optim.Adam(estimator.parameters(), lr=opt.lr)

opt.outf是各自数据集保存的模型路径,上述代码是因为前面的训练过程中可能会发生中断,但会保存训练的模型,在下次训练中如果使用--resume_posenet指定先前训练的posenet模型路径,就会加载先前训练的模型继续训练,--resume_refinenet也一样,如果指定先前训练的模型地址,则会加载模型,并设置学习率和权重衰减,否则视为还没开始refine和衰减过程。

    #加载训练数据集
    if opt.dataset == 'ycb':
        dataset = PoseDataset_ycb('train', opt.num_points, True, opt.dataset_root, opt.noise_trans, opt.refine_start)
    elif opt.dataset == 'linemod':
        dataset = PoseDataset_linemod('train', opt.num_points, True, opt.dataset_root, opt.noise_trans, opt.refine_start)
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=True, num_workers=opt.workers)
    #加载测试数据集
    if opt.dataset == 'ycb':
        test_dataset = PoseDataset_ycb('test', opt.num_points, False, opt.dataset_root, 0.0, opt.refine_start)
    elif opt.dataset == 'linemod':
        test_dataset = PoseDataset_linemod('test', opt.num_points, False, opt.dataset_root, 0.0, opt.refine_start)
    testdataloader = torch.utils.data.DataLoader(test_dataset, batch_size=1, shuffle=False, num_workers=opt.workers)

加载训练数据集和测试数据集,Dataloader使用opt.workers个进程加速读取数据。

    opt.sym_list = dataset.get_sym_list()
    opt.num_points_mesh = dataset.get_num_points_mesh()

    print('>>>>>>>>----------Dataset loaded!---------<<<<<<<<\nlength of the training set: {0}\nlength of the testing set: {1}\nnumber of sample points on mesh: {2}\nsymmetry object list: {3}'.format(len(dataset), len(test_dataset), opt.num_points_mesh, opt.sym_list))

获取该数据集的对称物体编号列表、mesh点数。 

    #定义Loss计算
    criterion = Loss(opt.num_points_mesh, opt.sym_list)
    criterion_refine = Loss_refine(opt.num_points_mesh, opt.sym_list)

对loss进行初始化,分别定义loss和loss_refine,详见loss.pyloss_refiner.py

    best_test = np.Inf

将最好模型的loss值best_test设置成无穷大。

    if opt.start_epoch == 1:
        for log in os.listdir(opt.log_dir):
            os.remove(os.path.join(opt.log_dir, log))
    st_time = time.time()

如果开始训练的epoch为1,则视为重头开始训练,就将之前训练的log文件全都删除。并记录开始时间。

然后进入epoch循环,下面的所有代码都在这个循环里面。

    for epoch in range(opt.start_epoch, opt.nepoch): #开始训练的epoch和最大的epoch
        #保存每次训练的log文件
        logger = setup_logger('epoch%d' % epoch, os.path.join(opt.log_dir, 'epoch_%d_log.txt' % epoch))
        logger.info('Train time {0}'.format(time.strftime("%Hh %Mm %Ss", time.gmtime(time.time() - st_time)) + ', ' + 'Training started'))
        train_count = 0 #记录训练次数
        train_dis_avg = 0.0 
        #选择是否开始refine过程
        if opt.refine_start:
            estimator.eval()
            refiner.train()
        else:
            estimator.train()
        optimizer.zero_grad() #将梯度初始化为0

如果开始refine过程了,那么姿势估计网络posenet开始eval模式,迭代自优化网络开始训练train,否则,posenet还是训练模式train。

        #每个epoch重复训练的次数
        for rep in range(opt.repeat_epoch):
            for i, data in enumerate(dataloader, 0): 
                points, choose, img, target, model_points, idx = data
                points, choose, img, target, model_points, idx = Variable(points).cuda(), \
                                                                 Variable(choose).cuda(), \
                                                                 Variable(img).cuda(), \
                                                                 Variable(target).cuda(), \
                                                                 Variable(model_points).cuda(), \
                                                                 Variable(idx).cuda()

enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,这个时候会调用dataset的__getitem__函数,获取一项数据,详见dataset.py。这里获取的变量含义如下:

points:由深度图转换成点云并随机筛选500个点,相机坐标系。

choose:所选择500个点云的索引,[bs, 1, 500]

img:通过语义分割之后剪切下来的RGB图像

target:根据model_points点云信息,以及标准旋转偏移矩阵转换过的目标点云[bs,500,3]

model_points:目标初始帧(模型)对应的点云信息[bs,500,3]

idx:目标物体类别

获取这些数据之后,将截取的RGB图像、筛选的点云、索引和物体类别输入到PoseNet姿态估计网络中进行训练,详见network.py

                #使用PoseNet进行姿势估计
                pred_r, pred_t, pred_c, emb = estimator(img, points, choose, idx)

 网络的输出如下:

pred_r: 预测的旋转参数[bs, 500, 4],每个像素都有一个预测

pred_t: 预测的偏移参数[bs, 500, 3],每个像素都有一个预测

pred_c: 预测的置信度[bs, 500, 1],置信度,每个像素都有一个预测

emb: 经过choose操作之后的img,与点云一一对应

 得到预测的pose和置信度之后,开始计算loss:

                #计算loss
                loss, dis, new_points, new_target = criterion(pred_r, pred_t, pred_c, target, model_points, idx, points, opt.w, opt.refine_start)

将预测值、目标点云、初始帧点云模型、编号、筛选的500个点云、平衡超参数等作为输入计算loss,详见loss.py。

                if opt.refine_start: #如果开始了refine过程
                    for ite in range(0, opt.iteration):
                        pred_r, pred_t = refiner(new_points, emb, idx)
                        dis, new_points, new_target = criterion_refine(pred_r, pred_t, new_target, model_points, idx, new_points)
                        dis.backward()
                else:
                    loss.backward()

这个部分是refine的部分,如果没有开始refine就直接对loss进行反向传播,如果开始了refine过程,则将上述loss计算输出的由预测pose和points逆转而来的new_points作为PoseRefineNet网络的输入,与经过choose之后的rbg图像一起,进行网络的训练,具体训练过程详见network.py。网络输出纠正的pose,然后计算refine过程的loss,这个loss只有一个像素的输出,该像素置信度最大,同样进行反向传播。而这个refine过程可以设置循环次数iteration,默认为2。

                train_dis_avg += dis.item()
                train_count += 1

                if train_count % opt.batch_size == 0:
                    logger.info('Train time {0} Epoch {1} Batch {2} Frame {3} Avg_dis:{4}'.format(time.strftime("%Hh %Mm %Ss", time.gmtime(time.time() - st_time)), epoch, int(train_count / opt.batch_size), train_count, train_dis_avg / opt.batch_size))
                    optimizer.step()
                    optimizer.zero_grad()
                    train_dis_avg = 0

                if train_count != 0 and train_count % 1000 == 0:
                    if opt.refine_start:
                        torch.save(refiner.state_dict(), '{0}/pose_refine_model_current.pth'.format(opt.outf))
                    else:
                        torch.save(estimator.state_dict(), '{0}/pose_model_current.pth'.format(opt.outf))

        print('>>>>>>>>----------epoch {0} train finish---------<<<<<<<<'.format(epoch))

上述代码实现每一个batch输出log信息,每1000次训练保存一个模型,如果已有refine过程则保存refine模型,如果没有则保存estimator模型。

到此训练过程结束。下面开始测试过程。

        #保存每次测试的log文件
        logger = setup_logger('epoch%d_test' % epoch, os.path.join(opt.log_dir, 'epoch_%d_test_log.txt' % epoch))
        logger.info('Test time {0}'.format(time.strftime("%Hh %Mm %Ss", time.gmtime(time.time() - st_time)) + ', ' + 'Testing started'))
        test_dis = 0.0
        test_count = 0
        #构建验证模型
        estimator.eval()
        refiner.eval()

训练完train样本后,生成的模型model要用来测试样本。这里须将模型设置为eval模式,否则的话,有输入数据,即使不训练,它也会改变权值。这是model中含有BN层和Dropout所带来的的性质。具体参考Pytorch基础 | eval()的用法比较_公众号机器学习与生成对抗网络的博客-CSDN博客

        #下面是对测试数据集进行测试的过程
        for j, data in enumerate(testdataloader, 0):
            #获取测试数据的各个值
            points, choose, img, target, model_points, idx = data
            points, choose, img, target, model_points, idx = Variable(points).cuda(), \
                                                             Variable(choose).cuda(), \
                                                             Variable(img).cuda(), \
                                                             Variable(target).cuda(), \
                                                             Variable(model_points).cuda(), \
                                                             Variable(idx).cuda()

 同样地,对测试数据进行预处理,获取用于测试的点云、RGB等,进行格式转换。

            #进行PoseNet姿势估计
            pred_r, pred_t, pred_c, emb = estimator(img, points, choose, idx)
            #计算损失
            _, dis, new_points, new_target = criterion(pred_r, pred_t, pred_c, target, model_points, idx, points, opt.w, opt.refine_start)

用PoseNet计算姿态,对姿态计算loss(但这里只输出dis,为最大置信度像素的loss)。 

            #如果开始了refine过程
            if opt.refine_start:
                for ite in range(0, opt.iteration):
                    #计算每次refine的pose和loss
                    pred_r, pred_t = refiner(new_points, emb, idx)
                    dis, new_points, new_target = criterion_refine(pred_r, pred_t, new_target, model_points, idx, new_points)

如果有refine,则将上一次预测姿态逆转的点云作为输入,用PoseRefineNet计算新的pose,然后计算refine过程的loss(也就是dis) 

            #输出log
            test_dis += dis.item()
            logger.info('Test time {0} Test Frame No.{1} dis:{2}'.format(time.strftime("%Hh %Mm %Ss", time.gmtime(time.time() - st_time)), test_count, dis))

            test_count += 1
        #计算测试过程的平均dis,输出log
        test_dis = test_dis / test_count
        logger.info('Test time {0} Epoch {1} TEST FINISH Avg dis: {2}'.format(time.strftime("%Hh %Mm %Ss", time.gmtime(time.time() - st_time)), epoch, test_dis))

将所有dis相加求平均,得到测试过程的平均dis,测试过程结束,到此,就完成了每次epoch的训练和测试步骤,下面的代码是为了下次epoch做准备。

        if test_dis <= best_test:
            best_test = test_dis
            if opt.refine_start:
                torch.save(refiner.state_dict(), '{0}/pose_refine_model_{1}_{2}.pth'.format(opt.outf, epoch, test_dis))
            else:
                torch.save(estimator.state_dict(), '{0}/pose_model_{1}_{2}.pth'.format(opt.outf, epoch, test_dis))
            print(epoch, '>>>>>>>>----------BEST TEST MODEL SAVED---------<<<<<<<<')

如果测试的dis小于最好的dis(初始best_dis为无穷大),就将test_dis作为best_dis,然后保存本次epoch最好的模型(如果有refine过程就保存refiner,没有就保存estimator)。

下面的代码是用来判断是否开始权重衰减,一旦开始了之后,就不会再执行以下的代码。

        if best_test < opt.decay_margin and not opt.decay_start:
            opt.decay_start = True
            opt.lr *= opt.lr_rate
            opt.w *= opt.w_rate
            optimizer = optim.Adam(estimator.parameters(), lr=opt.lr)

判断当前模型的损失值是否达到规定的临界值,如果达到了就开始进行学习率和权重的衰减。

下面的代码是用来判断是否开始refine过程,和衰减过程一样,一旦开始refine,就不会执行以下代码。

        if best_test < opt.refine_margin and not opt.refine_start:
            opt.refine_start = True
            opt.batch_size = int(opt.batch_size / opt.iteration)
            optimizer = optim.Adam(refiner.parameters(), lr=opt.lr)

            if opt.dataset == 'ycb':
                dataset = PoseDataset_ycb('train', opt.num_points, True, opt.dataset_root, opt.noise_trans, opt.refine_start)
            elif opt.dataset == 'linemod':
                dataset = PoseDataset_linemod('train', opt.num_points, True, opt.dataset_root, opt.noise_trans, opt.refine_start)
            dataloader = torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=True, num_workers=opt.workers)
            if opt.dataset == 'ycb':
                test_dataset = PoseDataset_ycb('test', opt.num_points, False, opt.dataset_root, 0.0, opt.refine_start)
            elif opt.dataset == 'linemod':
                test_dataset = PoseDataset_linemod('test', opt.num_points, False, opt.dataset_root, 0.0, opt.refine_start)
            testdataloader = torch.utils.data.DataLoader(test_dataset, batch_size=1, shuffle=False, num_workers=opt.workers)
            
            opt.sym_list = dataset.get_sym_list()
            opt.num_points_mesh = dataset.get_num_points_mesh()

            print('>>>>>>>>----------Dataset loaded!---------<<<<<<<<\nlength of the training set: {0}\nlength of the testing set: {1}\nnumber of sample points on mesh: {2}\nsymmetry object list: {3}'.format(len(dataset), len(test_dataset), opt.num_points_mesh, opt.sym_list))

            criterion = Loss(opt.num_points_mesh, opt.sym_list)
            criterion_refine = Loss_refine(opt.num_points_mesh, opt.sym_list)

可以看作,refine过程的迭代是在全局一次迭代之中进行的,但这里的batch_size对加载数据并没有什么影响,因为所有DataLoader中的batch_size都为1,可能是因为语义分割之后的图片大小都不一样,只能一个一个处理。然后重新加载refine过程的数据,定义refine过程的loss计算。

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
train.py是YOLOv5项目中的一个关键文件,用于训练模型。在train.py中,有几个重要的函数和配置信息。 首先是parse_opt函数,该函数用于解析命令行参数,包括模型的配置文件、数据集的路径、训练参数等等。\[3\] 接下来是main函数,该函数用于打印关键词和安装环境,判断是否进行断点训练和分布式训练,以及是否进行进化训练/遗传算法调参。\[3\] train函数是训练模型的核心函数,其中包含了一系列的配置信息和操作。首先是基本配置信息,包括模型的选择、损失函数的选择、训练的epoch数等等。然后是模型的加载和断点训练的设置,可以从之前的训练中继续训练模型。接着是冻结训练和冻结层设置,可以选择是否冻结部分层进行训练。还有图片大小和batch size的设置,以及优化器的选择和分组优化设置。此外,还包括学习率的设置、指数移动平均(EMA)的使用、归一化和单机多卡训练的配置。数据加载和anchor调整也是train函数中的一部分。最后,train函数包括了训练的配置,如多尺度训练和热身训练,以及训练结束后的打印信息和结果保存。\[3\] 除了上述函数外,还有run函数用于运行整个训练过程。全部代码都有详细的注释和使用教程,方便用户理解和使用。\[3\] 综上所述,train.py是YOLOv5项目中用于训练模型的关键文件,其中包含了各种配置信息和函数,用于控制训练过程和保存结果。\[3\] #### 引用[.reference_title] - *1* *2* [yolov5代码解读--train.py](https://blog.csdn.net/weixin_43337201/article/details/109389044)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [yolov5——train.py代码【注释、详解、使用教程】](https://blog.csdn.net/CharmsLUO/article/details/123542598)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Panpanpan!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值