上篇我们讲解了如何进行数据预处理,读取数据。接下来我们一起分析yolov3训练过程与training procedure。想真正读懂这个部分,要对inference部分有所了解。

数据加载

def train(cfg,data_cfg,img_size=416,resume=False,epochs=273,  # 500200 batches at bs 64, dataset length 117263batch_size=16,accumulate=1,multi_scale=False,freeze_backbone=False,transfer=False  # Transfer learning (train only YOLO layers)
):init_seeds()weights = 'weights' + os.sep   #python是跨平台的。在Windows上,文件的路径分隔符是'\',在Linux上是'/'。latest = weights + 'latest.pt'best = weights + 'best.pt'device = torch_utils.select_device()# Configure runtrain_path = parse_data_cfg(data_cfg)['train']  #data_cfg='data/coco.data' train=../coco/trainvalno5k.txt# Initialize modelmodel = Darknet(cfg, img_size).to(device)# Optimizeroptimizer = optim.SGD(model.parameters(), lr=hyp['lr0'], momentum=hyp['momentum'], weight_decay=hyp['weight_decay'])cutoff = -1  # backbone reaches to cutoff layerstart_epoch = 0best_loss = float('inf')nf = int(model.module_defs[model.yolo_layers[0] - 1]['filters'])  # yolo layer size (i.e. 255)if resume:  # Load previously saved modelif transfer:  # Transfer learningchkpt = torch.load(weights + 'yolov3.pt', map_location=device)model.load_state_dict({k: v for k, v in chkpt['model'].items() if v.numel() > 1 and v.shape[0] != 255},strict=False)for p in model.parameters():p.requires_grad = True if p.shape[0] == nf else Falseelse:  # resume from latest.ptchkpt = torch.load(latest, map_location=device)  # load checkpointmodel.load_state_dict(chkpt['model'])start_epoch = chkpt['epoch'] + 1if chkpt['optimizer'] is not None:optimizer.load_state_dict(chkpt['optimizer'])best_loss = chkpt['best_loss']del chkptelse:  # Initialize model with backbone (optional)if '-tiny.cfg' in cfg:cutoff = load_darknet_weights(model, weights + 'yolov3-tiny.conv.15')else:cutoff = load_darknet_weights(model, weights + 'darknet53.conv.74')lf = lambda x: 1 - 10 ** (hyp['lrf'] * (1 - x / epochs))  # inv exp ramp to lr0 * 1e-2scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lf, last_epoch=start_epoch - 1)dataset = LoadImagesAndLabels(train_path, img_size=img_size, augment=True)# dataset.__getitem__(0)# Initialize distributed trainingif torch.cuda.device_count() > 1:dist.init_process_group(backend=opt.backend, init_method=opt.dist_url, world_size=opt.world_size, rank=opt.rank)model = torch.nn.parallel.DistributedDataParallel(model)sampler = torch.utils.data.distributed.DistributedSampler(dataset)else:sampler = None# Dataloaderdataloader = DataLoader(dataset,batch_size=batch_size,num_workers=opt.num_workers,shuffle=True,pin_memory=True,collate_fn=dataset.collate_fn,sampler=sampler)

这一段代码实现了上一篇讲的数据加载过程,以及路径配置和模式选择,具体我不赘述,如果想了解的话可以在评论问我。

train以及loss

 for epoch in range(start_epoch, epochs):model.train()print(('\n%8s%12s' + '%10s' * 7) % ('Epoch', 'Batch', 'xy', 'wh', 'conf', 'cls', 'total', 'nTargets', 'time'))# Update schedulerscheduler.step()# Freeze backbone at epoch 0, unfreeze at epoch 1if freeze_backbone and epoch < 2:for name, p in model.named_parameters():if int(name.split('.')[1]) < cutoff:  # if layer < 75p.requires_grad = False if epoch == 0 else Truemloss = torch.zeros(5).to(device)  # mean lossesfor i, (imgs, targets, _, _) in enumerate(dataloader):imgs = imgs.to(device)targets = targets.to(device)nt = len(targets)# if nt == 0:  # if no targets continue#     continue# Plot images with bounding boxesif epoch == 0 and i == 0:plot_images(imgs=imgs, targets=targets, fname='train_batch0.jpg')# SGD burn-inif epoch == 0 and i <= n_burnin:lr = hyp['lr0'] * (i / n_burnin) ** 4for x in optimizer.param_groups:x['lr'] = lr# Run modelpred = model(imgs)# Compute lossloss, loss_items = compute_loss(pred, targets, model)

train的过程:
1:设置epoch参数,它决定了所有数据所需要训练的轮数。

2:进入epoch的for循环后,讲model设置为train,然后for i, (imgs, targets, _, _) in enumerate(dataloader):获取数据预处理后的数据和labels,这里要注意数据和labels都resize成416*416了(与txt中的不同)。

3:将取出的数据imgs传入model中,model就是yolov3的darknet,它有3个yolo层,每个yolo层都会输出一个特征映射图(dimention如(bs, 3, 13, 13, 85))bs=batch_size,3指每一个像素点存在3种anchor,13*13是它的维度,85=xywh+conf+classes。

class YOLOLayer(nn.Module):#x = module[0](x, img_size)def __init__(self, anchors, nc, img_size, yolo_layer, cfg):super(YOLOLayer, self).__init__()self.anchors = torch.Tensor(anchors)self.na = len(anchors)  # number of anchors (3)self.nc = nc  # number of classes (80)self.img_size = 0if ONNX_EXPORT:  # grids must be computed in __init__stride = [32, 16, 8][yolo_layer]  # stride of this layerif cfg.endswith('yolov3-tiny.cfg'):stride *= 2ng = (int(img_size[0] / stride), int(img_size[1] / stride))  # number grid pointscreate_grids(self, max(img_size), ng)def forward(self, p, img_size, var=None):if ONNX_EXPORT:bs = 1  # batch sizeelse:bs, nx, ny = p.shape[0], p.shape[-2], p.shape[-1]if self.img_size != img_size:create_grids(self, img_size, (nx, ny), p.device)# p.view(bs, 255, 13, 13) -- > (bs, 3, 13, 13, 85)  # (bs, anchors, grid, grid, classes + xywh)p = p.view(bs, self.na, self.nc + 5, self.nx, self.ny).permute(0, 1, 3, 4, 2).contiguous()  # predictionif self.training:return p

4:loss, loss_items = compute_loss(pred, targets, model),三个yolo层的输出最终与labels产生的targets运算得到loss。

5:用设置好的optimizer对loss进行BP。

6:保存最终的参数。

loss的构成:
首先,我从yolov3的算法思想讲起,让大家对整体思路有所了解,再具体到代码层面,这样大家可以感受代码复现算法的过程,从而真正理解yolov3。这对大家再相应背景下训练自己的数据也好,改造yolov3也好,都有直接的帮助。

yolov3算法思想:首先设计darknet,darknet是resnet的变形,在imagenet数据集上进行训练。然后去除最后的全连接,并在模型上增加了三个维度13,26,52的输出,即yolo层(为了增加小模型的检测精度),再用coco数据集进行微调。coco数据集的标签包括了class与ground truth(xywh)。loss由四个部分组成lxy,lwh,lcls,lconf组成(代码中会解释每个成分)。
这里就有个重要的疑问了,一个尺度的feature map有三个anchors,那么对于某个ground truth框,究竟是哪个anchor负责匹配它呢?和YOLOv1一样,对于训练图片中的ground truth,若其中心点落在某个cell内,那么该cell内的3个anchor box负责预测它,具体是哪个anchor box预测它,需要在训练中确定,即由那个与ground truth的IOU最大的anchor box预测它,而剩余的2个anchor box不与该ground truth匹配。YOLOv3需要假定每个cell至多含有一个grounth truth,而在实际上基本不会出现多于1个的情况。与ground truth匹配的anchor box计算坐标误差、置信度误差(此时target为1)以及分类误差,而其它的anchor box只计算置信度误差(此时target为0)。

def compute_loss(p, targets, model):  # predictions, targets, modelft = torch.cuda.FloatTensor if p[0].is_cuda else torch.Tensorlxy, lwh, lcls, lconf = ft([0]), ft([0]), ft([0]), ft([0])txy, twh, tcls, indices = build_targets(model, targets)#在13 26 52维度中找到大于iou阈值最适合的anchor box 作为targets#txy[维度(0:2),(x,y)] twh[维度(0:2),(w,h)] indices=[0,anchor索引,gi,gj]# Define criteriaMSE = nn.MSELoss()CE = nn.CrossEntropyLoss()BCE = nn.BCEWithLogitsLoss()# Compute lossesh = model.hyp  # hyperparametersbs = p[0].shape[0]  # batch sizek = h['k'] * bs  # loss gainfor i, pi0 in enumerate(p):  # layer i predictions, ib, a, gj, gi = indices[i]  # image, anchor, gridx, gridytconf = torch.zeros_like(pi0[..., 0])  # conf# Compute lossesif len(b):  # number of targetspi = pi0[b, a, gj, gi]  # predictions closest to anchors 找到p中与targets对应的数据lxytconf[b, a, gj, gi] = 1  # conf# pi[..., 2:4] = torch.sigmoid(pi[..., 2:4])  # wh power loss (uncomment)lxy += (k * h['xy']) * MSE(torch.sigmoid(pi[..., 0:2]), txy[i])  # xy losslwh += (k * h['wh']) * MSE(pi[..., 2:4], twh[i])  # wh yolo losslcls += (k * h['cls']) * CE(pi[..., 5:], tcls[i])  # class_conf loss# pos_weight = ft([gp[i] / min(gp) * 4.])# BCE = nn.BCEWithLogitsLoss(pos_weight=pos_weight)lconf += (k * h['conf']) * BCE(pi0[..., 4], tconf)  # obj_conf lossloss = lxy + lwh + lconf + lclsreturn loss, torch.cat((lxy, lwh, lconf, lcls, loss)).detach()

下面我们具体看看代码。
txy, twh, tcls, indices = build_targets(model, targets),build_targets函数返回了我们需要参数,我们看看这个函数具体再操作什么。

def build_targets(model, targets):# targets = [image, class, x(归一后的中心), y, w(归一后的宽), h] [ 0.00000, 20.00000,  0.72913,  0.48770,  0.13595,  0.08381]iou_thres = model.hyp['iou_t']  # hyperparameterif type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel):model = model.modulent = len(targets)txy, twh, tcls, indices = [], [], [], []for i in model.yolo_layers:layer = model.module_list[i][0]#layer->YOLOLayer()# iou of targets-anchorst, a = targets, []gwh = targets[:, 4:6] * layer.ng  #layer.ng就是yolo层输出维度13  26  52,gwh将原来的wh还原到13*13的图上if nt:iou = [wh_iou(x, gwh) for x in layer.anchor_vec] #anchor_vec是anchor box的whiou, a = torch.stack(iou, 0).max(0)  # best iou and anchor找到每一层与label->wh,iou最大的anchor,a是anchor的索引# reject below threshold ious (OPTIONAL, increases P, lowers R)reject = Trueif reject:j = iou > iou_threst, a, gwh = targets[j], a[j], gwh[j]# Indices targets = [image, class, x(归一后的中心), y, w(归一后的宽), h]b, c = t[:, :2].long().t()  # target image, classgxy = t[:, 2:4] * layer.nggi, gj = gxy.long().t()  # grid_i, grid_jindices.append((b, a, gj, gi))# XY coordinatestxy.append(gxy - gxy.floor())#在yolov3里是Gx,Gy减去grid cell左上角坐标Cx,Cy# Width and heighttwh.append(torch.log(gwh / layer.anchor_vec[a]))  # wh yolo method# twh.append((gwh / layer.anchor_vec[a]) ** (1 / 3) / 2)  # wh power method# Classtcls.append(c)if c.shape[0]:assert c.max() <= layer.nc, 'Target classes exceed model classes'return txy, twh, tcls, indices

build_targets(model, targets)需要两个参数,model是模型,targets是从labels读取resize后的标签参数,具体的targets = [image, class, x(归一后的中心), y, w(归一后的宽), h],iou_thres是我们需要设置的超参数,作用我们后面会讲。

for i in model.yolo_layers:我们知道有3个yolo层,i是层数的索引,targets[:, 4:6]是ground truth的宽和高,所以gwh将原来的wh还原到13*13的特征图上。

iou = [wh_iou(x, gwh) for x in layer.anchor_vec],anchor_vec是anchor box的wh,我们求出每一层三个anchor box与ground truth的iou,然后找到iou最大的anchor box,用a来记录它的索引,删去小于iou_thres的anchor。用b记录imges,c记录calss类别,gxy记录对应feature map的中心点,gi,gj记录哪个格子负责这个ground truth。

txy=gxy - gxy.floor(),在yolov3里是Gx,Gy减去grid cell左上角坐标Cx,Cy得到txy。

twh=torch.log(gwh / layer.anchor_vec[a])对应上面公式反推过来的。txy,twh是我们loss的标签,它是真正坐标的offset,所以我们bp优化得到的ixy还需要做相应的变换才能得到真正的box。

tcls=c,c就是calss类别。这样我们就理解build_targets返回的标签参数意义了。

    for i, pi0 in enumerate(p):  # layer i predictions, ib, a, gj, gi = indices[i]  # image, anchor, gridx, gridytconf = torch.zeros_like(pi0[..., 0])  # conf# Compute lossesif len(b):  # number of targetspi = pi0[b, a, gj, gi]  # predictions closest to anchors 找到p中与targets对应的数据lxytconf[b, a, gj, gi] = 1  # conf# pi[..., 2:4] = torch.sigmoid(pi[..., 2:4])  # wh power loss (uncomment)lxy += (k * h['xy']) * MSE(torch.sigmoid(pi[..., 0:2]), txy[i])  # xy losslwh += (k * h['wh']) * MSE(pi[..., 2:4], twh[i])  # wh yolo losslcls += (k * h['cls']) * CE(pi[..., 5:], tcls[i])  # class_conf loss# pos_weight = ft([gp[i] / min(gp) * 4.])# BCE = nn.BCEWithLogitsLoss(pos_weight=pos_weight)lconf += (k * h['conf']) * BCE(pi0[..., 4], tconf)  # obj_conf lossloss = lxy + lwh + lconf + lcls

for i, pi0 in enumerate( p ):p是我们yolo层返回的特征图,i=0时,p=[[bs,3,13,13,85],[bs,3,26,26,85],[bs,3,52,52,85]].

b, a, gj, gi = indices[i],从返回的标签中读取最佳anchor,与格子坐标,因为我们知道每个ground truth只对应一个anchor box,这些索引就是为了找到那个anchor box。

pi = pi0[b, a, gj, gi],pi0=(b,3,13,13,85)找到pi0中对应ground truth的数据。

tconf[b, a, gj, gi] = 1 ,将对应位置的confience设置为1,其余都为0。

lxy += (k * h[‘xy’]) * MSE(torch.sigmoid(pi[…, 0:2]), txy[i]),与ground truth匹配的anchor box计算坐标误差、置信度误差(此时target为1)以及分类误差,而其它的anchor box只计算置信度误差(此时target为0)。这里pi只记录了与ground truth匹配的anchor box的信息。sigmoid是将xy限制在0-1,因为中心坐标落在格子内。MSE就是均方差。

lwh += (k * h[‘wh’]) * MSE(pi[…, 2:4], twh[i]) 如上。

lcls += (k * h[‘cls’]) * CE(pi[…, 5:], tcls[i]):只计算与ground truth匹配的anchor box的分类误差。CE是交叉熵。

lconf += (k * h[‘conf’]) * BCE(pi0[…, 4], tconf)。tconf把对应ground truth的置信度设置为1,没有与ground truth对应都设置为0.

这样我们就求得了loss,有些地方可能描述不太好,当然也有理解不到位的,欢迎讨论。

Pytorch实现yolov3(train)训练代码详解(二)相关推荐

  1. Pytorch | yolov3原理及代码详解(二)

    阅前可看: Pytorch | yolov3原理及代码详解(一) https://blog.csdn.net/qq_24739717/article/details/92399359 分析代码: ht ...

  2. Pytorch|YOWO原理及代码详解(二)

    Pytorch|YOWO原理及代码详解(二) 本博客上接,Pytorch|YOWO原理及代码详解(一),阅前可看. 1.正式训练 if opt.evaluate:logging('evaluating ...

  3. 【Image captioning】Show, Attend, and Tell 从零到掌握之三--train.py代码详解

    [Image captioning]Show, Attend, and Tell 从零到掌握之三–train.py代码详解 作者:安静到无声 个人主页 作者简介:人工智能和硬件设计博士生.CSDN与阿 ...

  4. Pytorch | yolov3原理及代码详解(一)

    YOLO相关原理 : https://blog.csdn.net/leviopku/article/details/82660381 https://www.jianshu.com/p/d13ae10 ...

  5. 【CV】Pytorch一小时入门教程-代码详解

    目录 一.关键部分代码分解 1.定义网络 2.损失函数(代价函数) 3.更新权值 二.训练完整的分类器 1.数据处理 2. 训练模型(代码详解) CPU训练 GPU训练 CPU版本与GPU版本代码区别 ...

  6. PyTorch 迁移学习 (Transfer Learning) 代码详解

    PyTorch 迁移学习 代码详解 概述 为什么使用迁移学习 更好的结果 节省时间 加载模型 ResNet152 冻层实现 模型初始化 获取需更新参数 训练模型 获取数据 完整代码 概述 迁移学习 ( ...

  7. 【Pytorch】构建VOC2012数据集代码详解

    目录 数据集 图片读入 预处理 crop 标签和像素点颜色 随机翻转 噪声 标准化 torch.utils.data.Dataset()和torch.utils.data.DataLoader() t ...

  8. ELMo代码详解(二)

    ELMo代码解读笔记2:模型代码 2.模型代码介绍   模型代码主要包括以下几个部分:1.构建word embedding; 2.构建word_char embedding的准备; 3.语言模型介绍( ...

  9. [Pytorch系列-61]:循环神经网络 - 中文新闻文本分类详解-3-CNN网络训练与评估代码详解

    作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客 本文网址:https://blog.csdn.net/HiWangWenBing/article/detai ...

最新文章

  1. 在main()之前,IAR都做了啥?
  2. ESP-TOUCH编码规则及解码
  3. Fluent Ribbon 第三步 应用程序菜单
  4. SAP CRM和C4C的订单Number range
  5. maven项目发布到tomcat里lib包没有发布的问题
  6. git php自动发布,使用 Git Hooks 实现自动部署PHP项目
  7. c语言程序设计字符处理周信东,“电子科技大学出版社(周信东主编)”的C语言程序设计实验-整理代码-.doc...
  8. 匿名对象方案与实体对象方案对比
  9. System学习笔记007---win10连接阿里云出现_远程连接提示要求的函数不受支持如何解决
  10. Python + selenium之组织unittest单元测试用例
  11. C++ 好的博客??
  12. c语言微信昵称大全女生,微信名字大全女生可爱
  13. C语言循环语句中 i++, ++i, i--, --i的使用
  14. MiniGUI源码分析:GDI(1)-- GDI概览及Surface
  15. Book Sharing
  16. 成都拓嘉启远:拼多多上产品清单的条件
  17. 奔三之际,任性一把 ——从华为南研所裸辞后的一些体会和感想
  18. 2017第三届美亚杯全国电子数据取证大赛团队赛write up
  19. 无线网卡的工作模式--ath9k网卡驱动开发总结(一)
  20. stm32独立看门狗

热门文章

  1. Godfather POJ - 3107 (求树的重心)
  2. C++ 数据结构之队列queue (henu.hjy)
  3. 自动驾驶系统进阶与项目实战(三)基于全卷积神经网络的点云三维目标检测和ROS实战
  4. CSS in Depth 学习札记之:猫头鹰选择器
  5. 逐梦....圈圈圈圈圈
  6. 安排 , 2021新冠疫情防控指挥作战平台(视频+课件+代码+资料)
  7. 服务器怎么部署静态网站,纯静态网站部署服务器
  8. python培训价格多少钱
  9. Git 经验总结及 Git GitHub 学习指南
  10. AI黑科技 | 宏碁研发智能穿戴设备:智能佛珠